From fa6e00c3ee0ef04061672bf47dc7f450599825f7 Mon Sep 17 00:00:00 2001 From: terrfly Date: Thu, 26 Aug 2021 16:57:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=AE=A2=E5=8D=95=E7=9A=84?= =?UTF-8?q?=E5=88=86=E8=B4=A6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/sql/init.sql | 47 +++- docs/sql/patch.sql | 99 ++++++++- .../mq/model/PayOrderDivisionMQ.java | 2 +- .../core/entity/MchDivisionReceiver.java | 21 +- .../core/entity/MchDivisionReceiverGroup.java | 78 +++++++ .../jeequan/jeepay/core/entity/PayOrder.java | 7 +- .../core/entity/PayOrderDivisionRecord.java | 16 +- .../core/model/DBApplicationConfig.java | 4 +- .../core/model/security/JeeUserDetails.java | 1 - .../jeequan/jeepay/core/utils/AmountUtil.java | 16 +- .../com/jeequan/jeepay/core/utils/RegKit.java | 6 + .../mgr/ctrl/order/PayOrderController.java | 3 + .../MchDivisionReceiverController.java | 204 ++++++++++++++++++ .../MchDivisionReceiverGroupController.java | 166 ++++++++++++++ .../PayOrderDivisionRecordController.java | 115 ++++++++++ .../MchPayInterfaceConfigController.java | 33 ++- .../mch/ctrl/order/PayOrderController.java | 3 + .../mch/ctrl/paytest/PaytestController.java | 12 +- .../pay/channel/AbstractPaymentService.java | 10 + .../jeepay/pay/channel/IDivisionService.java | 5 +- .../channel/alipay/AlipayDivisionService.java | 195 +++++++++++++++++ .../alipay/ctrl/AlipayBizController.java | 2 +- .../channel/wxpay/WxpayDivisionService.java | 96 +++++++-- .../channel/wxpay/WxpayPaymentService.java | 12 ++ .../pay/channel/wxpay/payway/WxBar.java | 5 + .../MchDivisionReceiverBindController.java | 199 +++++++++++++++++ .../payorder/AbstractPayOrderController.java | 1 - .../pay/ctrl/qr/ChannelUserIdController.java | 1 - .../jeepay/pay/model/AlipayClientWrapper.java | 2 - .../pay/mq/PayOrderDivisionMQReceiver.java | 62 ++++-- .../rqrs/division/DivisionReceiverBindRQ.java | 72 +++++++ .../rqrs/division/DivisionReceiverBindRS.java | 139 ++++++++++++ .../pay/service/ConfigContextService.java | 35 ++- .../pay/service/PayOrderProcessService.java | 2 +- .../jeequan/jeepay/pay/util/CodeImgUtil.java | 1 - .../impl/MchDivisionReceiverGroupService.java | 25 +++ .../impl/PayOrderDivisionRecordService.java | 26 +++ .../jeepay/service/impl/PayOrderService.java | 30 ++- .../MchDivisionReceiverGroupMapper.java | 16 ++ .../mapper/MchDivisionReceiverGroupMapper.xml | 17 ++ .../mapper/MchDivisionReceiverMapper.xml | 4 +- .../mapper/PayOrderDivisionRecordMapper.java | 3 + .../mapper/PayOrderDivisionRecordMapper.xml | 8 + .../jeepay/service/mapper/PayOrderMapper.xml | 1 - pom.xml | 4 +- 45 files changed, 1694 insertions(+), 112 deletions(-) create mode 100644 jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiverGroup.java create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/MchDivisionReceiverController.java create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/MchDivisionReceiverGroupController.java create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/PayOrderDivisionRecordController.java create mode 100644 jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayDivisionService.java create mode 100644 jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/division/MchDivisionReceiverBindController.java create mode 100644 jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/division/DivisionReceiverBindRQ.java create mode 100644 jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/division/DivisionReceiverBindRS.java create mode 100644 jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/MchDivisionReceiverGroupService.java create mode 100644 jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverGroupMapper.java create mode 100644 jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverGroupMapper.xml diff --git a/docs/sql/init.sql b/docs/sql/init.sql index f0e0efb..c858d1f 100644 --- a/docs/sql/init.sql +++ b/docs/sql/init.sql @@ -5,7 +5,7 @@ -- 权限表 DROP TABLE IF EXISTS `t_sys_entitlement`; CREATE TABLE `t_sys_entitlement` ( - `ent_id` VARCHAR(32) NOT NULL COMMENT '权限ID[ENT_功能模块_子模块_操作], eg: ENT_ROLE_LIST_ADD', + `ent_id` VARCHAR(64) NOT NULL COMMENT '权限ID[ENT_功能模块_子模块_操作], eg: ENT_ROLE_LIST_ADD', `ent_name` VARCHAR(32) NOT NULL COMMENT '权限名称', `menu_icon` VARCHAR(32) COMMENT '菜单图标', `menu_uri` VARCHAR(128) COMMENT '菜单uri/路由地址', @@ -261,7 +261,6 @@ CREATE TABLE `t_pay_order` ( `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-已发送', @@ -403,13 +402,27 @@ CREATE TABLE `t_transfer_order` ( INDEX(`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='转账订单表'; +-- 商户分账接收者账号组 +DROP TABLE IF EXISTS `t_mch_division_receiver_group`; +CREATE TABLE `t_mch_division_receiver_group` ( + `receiver_group_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '组ID', + `receiver_group_name` VARCHAR(64) NOT NULL COMMENT '组名称', + `mch_no` VARCHAR(64) NOT NULL COMMENT '商户号', + `auto_division_flag` TINYINT(6) NOT NULL DEFAULT 0 COMMENT '自动分账组(当订单分账模式为自动分账,改组将完成分账逻辑) 0-否 1-是', + `created_uid` BIGINT(20) NOT NULL COMMENT '创建者用户ID', + `created_by` VARCHAR(64) 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 (`receiver_group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=100001 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 '接收者账号别名', + `receiver_alias` VARCHAR(64) NOT NULL COMMENT '接收者账号别名', + `receiver_group_id` BIGINT(20) COMMENT '组ID(便于商户接口使用)', + `receiver_group_name` VARCHAR(64) COMMENT '组名称', `mch_no` VARCHAR(64) NOT NULL COMMENT '商户号', `isv_no` VARCHAR(64) COMMENT '服务商号', `app_id` VARCHAR(64) NOT NULL COMMENT '应用ID', @@ -421,14 +434,13 @@ CREATE TABLE `t_mch_division_receiver` ( `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_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='商户分账接收者账号绑定关系表'; +) ENGINE=InnoDB AUTO_INCREMENT=800001 DEFAULT CHARSET=utf8mb4 COMMENT='商户分账接收者账号绑定关系表'; -- 分账记录表 DROP TABLE IF EXISTS `t_pay_order_division_record`; @@ -449,7 +461,8 @@ CREATE TABLE `t_pay_order_division_record` ( `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(便于商户存储)', + `receiver_group_id` BIGINT(20) COMMENT '账号快照》 组ID(便于商户接口使用)', + `receiver_alias` VARCHAR(64) 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 '账号快照》 分账接收账号名称', @@ -630,6 +643,24 @@ insert into t_sys_entitlement values('ENT_ORDER', '订单中心', 'transaction', insert into t_sys_entitlement values('ENT_TRANSFER_ORDER_LIST', '页面:转账订单列表', 'no-icon', '', '', 'PB', 0, 1, 'ENT_TRANSFER_ORDER', '0', 'MCH', now(), now()); insert into t_sys_entitlement values('ENT_TRANSFER_ORDER_VIEW', '按钮:详情', 'no-icon', '', '', 'PB', 0, 1, 'ENT_TRANSFER_ORDER', '0', 'MCH', now(), now()); +-- 【商户系统】 分账管理 +insert into t_sys_entitlement values('ENT_DIVISION', '分账管理', 'apartment', '', 'RouteView', 'ML', 0, 1, 'ROOT', '30', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP', '账号组管理', 'team', '/divisionReceiverGroup', 'DivisionReceiverGroupPage', 'ML', 0, 1, 'ENT_DIVISION', '10', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_LIST', '页面:数据列表', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_VIEW', '按钮:详情', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_ADD', '按钮:新增', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_EDIT', '按钮:修改', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_DELETE', '按钮:删除', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER', '收款账号管理', 'trademark', '/divisionReceiver', 'DivisionReceiverPage', 'ML', 0, 1, 'ENT_DIVISION', '20', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_LIST', '页面:数据列表', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_VIEW', '按钮:详情', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_ADD', '按钮:新增收款账号', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_DELETE', '按钮:删除收款账号', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_EDIT', '按钮:修改账号信息', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECORD', '分账记录', 'unordered-list', '/divisionRecord', 'DivisionRecordPage', 'ML', 0, 1, 'ENT_DIVISION', '30', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECORD_LIST', '页面:数据列表', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECORD', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_DIVISION_RECORD_VIEW', '按钮:详情', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECORD', '0', 'MCH', now(), now()); + -- 【商户系统】 系统管理 insert into t_sys_entitlement values('ENT_SYS_CONFIG', '系统管理', 'setting', '', 'RouteView', 'ML', 0, 1, 'ROOT', '200', 'MCH', now(), now()); insert into t_sys_entitlement values('ENT_UR', '用户角色管理', 'team', '', 'RouteView', 'ML', 0, 1, 'ENT_SYS_CONFIG', '10', 'MCH', now(), now()); diff --git a/docs/sql/patch.sql b/docs/sql/patch.sql index baee5e8..00a8057 100644 --- a/docs/sql/patch.sql +++ b/docs/sql/patch.sql @@ -95,7 +95,7 @@ insert into t_sys_entitlement values('ENT_MCH_TRANSFER_DO', '按钮:发起转 ## -- ++++ ++++ -## -- ++++ [v1.6.0] ===> [未确定] ++++ +## -- ++++ [v1.6.0] ===> [v1.7.0] ++++ -- 订单页的支付方式筛选项添加权限并可分配: 避免API权限导致页面出现异常 insert into t_sys_entitlement values('ENT_PAY_ORDER_SEARCH_PAY_WAY', '筛选项:支付方式', 'no-icon', '', '', 'PB', 0, 1, 'ENT_PAY_ORDER', '0', 'MGR', now(), now()); @@ -105,10 +105,8 @@ insert into t_sys_entitlement values('ENT_PAY_ORDER_SEARCH_PAY_WAY', '筛选项 -- 插入表结构,并插入默认数据(默认费率 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`; @@ -117,7 +115,100 @@ alter table `t_pay_order` add column `division_mode` TINYINT(6) DEFAULT 0 COMMEN 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 分账的两张表 + +-- 商户分账接收者账号组 +DROP TABLE IF EXISTS `t_mch_division_receiver_group`; +CREATE TABLE `t_mch_division_receiver_group` ( + `receiver_group_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '组ID', + `receiver_group_name` VARCHAR(64) NOT NULL COMMENT '组名称', + `mch_no` VARCHAR(64) NOT NULL COMMENT '商户号', + `auto_division_flag` TINYINT(6) NOT NULL DEFAULT 0 COMMENT '自动分账组(当订单分账模式为自动分账,改组将完成分账逻辑) 0-否 1-是', + `created_uid` BIGINT(20) NOT NULL COMMENT '创建者用户ID', + `created_by` VARCHAR(64) 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 (`receiver_group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=100001 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_alias` VARCHAR(64) NOT NULL COMMENT '接收者账号别名', + `receiver_group_id` BIGINT(20) COMMENT '组ID(便于商户接口使用)', + `receiver_group_name` VARCHAR(64) 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_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=800001 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) COMMENT '账号快照》 组ID(便于商户接口使用)', + `receiver_alias` VARCHAR(64) 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) 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='分账记录表'; + +-- 权限表扩容 +alter table `t_sys_entitlement` modify column `ent_id` VARCHAR(64) NOT NULL COMMENT '权限ID[ENT_功能模块_子模块_操作], eg: ENT_ROLE_LIST_ADD'; + +-- 【商户系统】 分账管理 +insert into t_sys_entitlement values('ENT_DIVISION', '分账管理', 'apartment', '', 'RouteView', 'ML', 0, 1, 'ROOT', '30', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP', '账号组管理', 'team', '/divisionReceiverGroup', 'DivisionReceiverGroupPage', 'ML', 0, 1, 'ENT_DIVISION', '10', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_LIST', '页面:数据列表', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_VIEW', '按钮:详情', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_ADD', '按钮:新增', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_EDIT', '按钮:修改', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_GROUP_DELETE', '按钮:删除', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER_GROUP', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER', '收款账号管理', 'trademark', '/divisionReceiver', 'DivisionReceiverPage', 'ML', 0, 1, 'ENT_DIVISION', '20', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_LIST', '页面:数据列表', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_VIEW', '按钮:详情', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_ADD', '按钮:新增收款账号', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_DELETE', '按钮:删除收款账号', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECEIVER_EDIT', '按钮:修改账号信息', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECEIVER', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECORD', '分账记录', 'unordered-list', '/divisionRecord', 'DivisionRecordPage', 'ML', 0, 1, 'ENT_DIVISION', '30', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECORD_LIST', '页面:数据列表', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECORD', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_DIVISION_RECORD_VIEW', '按钮:详情', 'no-icon', '', '', 'PB', 0, 1, 'ENT_DIVISION_RECORD', '0', 'MCH', now(), now()); diff --git a/jeepay-components/jeepay-components-mq/src/main/java/com/jeequan/jeepay/components/mq/model/PayOrderDivisionMQ.java b/jeepay-components/jeepay-components-mq/src/main/java/com/jeequan/jeepay/components/mq/model/PayOrderDivisionMQ.java index d2b3547..f8fa73b 100644 --- a/jeepay-components/jeepay-components-mq/src/main/java/com/jeequan/jeepay/components/mq/model/PayOrderDivisionMQ.java +++ b/jeepay-components/jeepay-components-mq/src/main/java/com/jeequan/jeepay/components/mq/model/PayOrderDivisionMQ.java @@ -108,7 +108,7 @@ public class PayOrderDivisionMQ extends AbstractMQ { private Long receiverId; /** - * 多渠道组合ID(便于商户接口使用) (与 receiverId 二选一) + * 组ID(便于商户接口使用) (与 receiverId 二选一) */ private Long receiverGroupId; diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiver.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiver.java index 1e21218..7b877ad 100644 --- a/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiver.java +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiver.java @@ -28,11 +28,6 @@ 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 gw(){ return new LambdaQueryWrapper<>(); @@ -45,14 +40,19 @@ public class MchDivisionReceiver implements Serializable { private Long receiverId; /** - * 多渠道组合ID(便于商户接口使用) + * 接收者账号别名 + */ + private String receiverAlias; + + /** + * 组ID(便于商户接口使用) */ private Long receiverGroupId; /** - * 接收者账号别名 + * 组名称 */ - private String receiverName; + private String receiverGroupName; /** * 商户号 @@ -109,11 +109,6 @@ public class MchDivisionReceiver implements Serializable { */ private Byte state; - /** - * 上游绑定状态: 1-绑定成功, 2-绑定异常 - */ - private Byte channelBindState; - /** * 上游绑定返回信息,一般用作查询绑定异常时的记录 */ diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiverGroup.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiverGroup.java new file mode 100644 index 0000000..ba82575 --- /dev/null +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiverGroup.java @@ -0,0 +1,78 @@ +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.util.Date; + +/** + *

+ * 分账账号组 + *

+ * + * @author [mybatis plus generator] + * @since 2021-08-23 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("t_mch_division_receiver_group") +public class MchDivisionReceiverGroup implements Serializable { + + //gw + public static final LambdaQueryWrapper gw(){ + return new LambdaQueryWrapper<>(); + } + + private static final long serialVersionUID=1L; + + /** + * 组ID + */ + @TableId(value = "receiver_group_id", type = IdType.AUTO) + private Long receiverGroupId; + + /** + * 组名称 + */ + private String receiverGroupName; + + /** + * 商户号 + */ + private String mchNo; + + /** + * 自动分账组(当订单分账模式为自动分账,改组将完成分账逻辑) 0-否 1-是 + */ + private Byte autoDivisionFlag; + + /** + * 创建者用户ID + */ + private Long createdUid; + + /** + * 创建者姓名 + */ + private String createdBy; + + /** + * 创建时间 + */ + private Date createdAt; + + /** + * 更新时间 + */ + private Date updatedAt; + + + +} diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrder.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrder.java index 57c9e7c..2af6ec6 100644 --- a/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrder.java +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrder.java @@ -132,11 +132,6 @@ public class PayOrder extends BaseModel implements Serializable { */ private Long mchFeeAmount; - /** - * 商户入账金额(支付金额-手续费),单位分 - */ - private Long mchIncomeAmount; - /** * 三位货币代码,人民币:cny */ @@ -203,7 +198,7 @@ public class PayOrder extends BaseModel implements Serializable { private Byte divisionMode; /** - * 订单分账状态:0-未发生分账, 1-等待分账任务处理, 2-分账成功, 3-分账失败 + * 0-未发生分账, 1-等待分账任务处理, 2-分账处理中, 3-分账任务已结束(不体现状态) */ private Byte divisionState; diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrderDivisionRecord.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrderDivisionRecord.java index 8c30469..979bcdb 100644 --- a/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrderDivisionRecord.java +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrderDivisionRecord.java @@ -3,6 +3,7 @@ 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; @@ -25,8 +26,16 @@ import java.util.Date; @TableName("t_pay_order_division_record") public class PayOrderDivisionRecord implements Serializable { + public static final byte STATE_WAIT = 0; // 待分账 + public static final byte STATE_SUCCESS = 1; // 分账成功 + public static final byte STATE_FAIL = 2; // 分账失败 + private static final long serialVersionUID=1L; + public static final LambdaQueryWrapper gw(){ + return new LambdaQueryWrapper<>(); + } + /** * 分账记录ID */ @@ -109,10 +118,15 @@ public class PayOrderDivisionRecord implements Serializable { private Long receiverId; /** - * 账号快照》 多渠道组合ID(便于商户存储) + * 账号快照》 组ID(便于商户接口使用) */ private Long receiverGroupId; + /** + * 账号快照》 分账接收者别名 + */ + private String receiverAlias; + /** * 账号快照》 分账接收账号类型: 0-个人 1-商户 */ diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/DBApplicationConfig.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/DBApplicationConfig.java index 6efb936..809e0a8 100644 --- a/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/DBApplicationConfig.java +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/DBApplicationConfig.java @@ -51,12 +51,12 @@ public class DBApplicationConfig implements Serializable { /** 生成 【jsapi统一收银台】oauth2获取用户ID回调地址 **/ public String genOauth2RedirectUrlEncode(String payOrderId){ - return URLUtil.encode(getPaySiteUrl() + "/cashier/index.html#/oauth2Callback/" + JeepayKit.aesEncode(payOrderId)); + return URLUtil.encodeAll(getPaySiteUrl() + "/cashier/index.html#/oauth2Callback/" + JeepayKit.aesEncode(payOrderId)); } /** 生成 【商户获取渠道用户ID接口】oauth2获取用户ID回调地址 **/ public String genMchChannelUserIdApiOauth2RedirectUrlEncode(JSONObject param){ - return URLUtil.encode(getPaySiteUrl() + "/api/channelUserId/oauth2Callback/" + JeepayKit.aesEncode(param.toJSONString())); + return URLUtil.encodeAll(getPaySiteUrl() + "/api/channelUserId/oauth2Callback/" + JeepayKit.aesEncode(param.toJSONString())); } /** 生成 【jsapi统一收银台二维码图片地址】 **/ diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/security/JeeUserDetails.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/security/JeeUserDetails.java index baf2ba6..8148b6e 100644 --- a/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/security/JeeUserDetails.java +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/security/JeeUserDetails.java @@ -62,7 +62,6 @@ public class JeeUserDetails implements UserDetails { this.setSysUser(sysUser); this.setCredential(credential); - //TODO .... //做一些初始化操作 } diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/AmountUtil.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/AmountUtil.java index b13c8ce..5e30a83 100644 --- a/jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/AmountUtil.java +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/AmountUtil.java @@ -131,8 +131,22 @@ public class AmountUtil { * @param rate 费率 (保持与数据库的格式一致 ,真实费率值,如费率为0.55%,则传入 0.0055) */ public static Long calPercentageFee(Long amount, BigDecimal rate){ + return calPercentageFee(amount, rate, BigDecimal.ROUND_HALF_UP); + } + + /** + * 计算百分比类型的各种费用值 (订单金额 * 真实费率 结果四舍五入并保留0位小数 ) + * + * @author terrfly + * @site https://www.jeepay.vip + * @date 2021/8/20 14:53 + * @param amount 订单金额 (保持与数据库的格式一致 ,单位:分) + * @param rate 费率 (保持与数据库的格式一致 ,真实费率值,如费率为0.55%,则传入 0.0055) + * @param mode 模式 参考:BigDecimal.ROUND_HALF_UP(四舍五入) BigDecimal.ROUND_FLOOR(向下取整) + */ + public static Long calPercentageFee(Long amount, BigDecimal rate, int mode){ //费率乘以订单金额 结果四舍五入并保留0位小数 - return new BigDecimal(amount).multiply(rate).setScale(0, BigDecimal.ROUND_HALF_UP).longValue(); + return new BigDecimal(amount).multiply(rate).setScale(0, mode).longValue(); } } diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/RegKit.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/RegKit.java index a937f1b..61dc7e7 100644 --- a/jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/RegKit.java +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/RegKit.java @@ -25,10 +25,16 @@ package com.jeequan.jeepay.core.utils; public class RegKit { public static final String REG_MOBILE = "^1\\d{10}$"; //判断是否是手机号 + public static final String REG_ALIPAY_USER_ID = "^2088\\d{12}$"; //判断是支付宝用户Id 以2088开头的纯16位数字 + public static boolean isMobile(String str){ return match(str, REG_MOBILE); } + public static boolean isAlipayUserId(String str){ + return match(str, REG_ALIPAY_USER_ID); + } + /** 正则验证 */ public static boolean match(String text, String reg){ diff --git a/jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/order/PayOrderController.java b/jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/order/PayOrderController.java index eafecf0..a66d73e 100644 --- a/jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/order/PayOrderController.java +++ b/jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/order/PayOrderController.java @@ -100,6 +100,9 @@ public class PayOrderController extends CommonCtrl { if (StringUtils.isNotEmpty(payOrder.getAppId())) { wrapper.eq(PayOrder::getAppId, payOrder.getAppId()); } + if (payOrder.getDivisionState() != null) { + wrapper.eq(PayOrder::getDivisionState, payOrder.getDivisionState()); + } if (paramJSON != null) { if (StringUtils.isNotEmpty(paramJSON.getString("createdStart"))) { wrapper.ge(PayOrder::getCreatedAt, paramJSON.getString("createdStart")); diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/MchDivisionReceiverController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/MchDivisionReceiverController.java new file mode 100644 index 0000000..fd903da --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/MchDivisionReceiverController.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.mch.ctrl.division; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.jeequan.jeepay.JeepayClient; +import com.jeequan.jeepay.core.aop.MethodLog; +import com.jeequan.jeepay.core.constants.ApiCodeEnum; +import com.jeequan.jeepay.core.constants.CS; +import com.jeequan.jeepay.core.entity.MchApp; +import com.jeequan.jeepay.core.entity.MchDivisionReceiver; +import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup; +import com.jeequan.jeepay.core.exception.BizException; +import com.jeequan.jeepay.core.model.ApiRes; +import com.jeequan.jeepay.exception.JeepayException; +import com.jeequan.jeepay.mch.ctrl.CommonCtrl; +import com.jeequan.jeepay.model.DivisionReceiverBindReqModel; +import com.jeequan.jeepay.request.DivisionReceiverBindRequest; +import com.jeequan.jeepay.response.DivisionReceiverBindResponse; +import com.jeequan.jeepay.service.impl.MchAppService; +import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService; +import com.jeequan.jeepay.service.impl.MchDivisionReceiverService; +import com.jeequan.jeepay.service.impl.SysConfigService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.math.BigDecimal; + +/** + * 商户分账接收者账号关系维护 + * + * @author terrfly + * @site https://www.jeequan.com + * @date 2021-08-23 11:50 + */ +@RestController +@RequestMapping("api/divisionReceivers") +public class MchDivisionReceiverController extends CommonCtrl { + + @Autowired private MchDivisionReceiverService mchDivisionReceiverService; + @Autowired private MchDivisionReceiverGroupService mchDivisionReceiverGroupService; + @Autowired private MchAppService mchAppService; + @Autowired private SysConfigService sysConfigService; + + + /** list */ + @PreAuthorize("hasAnyAuthority( 'ENT_DIVISION_RECEIVER_LIST' )") + @RequestMapping(value="", method = RequestMethod.GET) + public ApiRes list() { + + MchDivisionReceiver queryObject = getObject(MchDivisionReceiver.class); + + LambdaQueryWrapper condition = MchDivisionReceiver.gw(); + condition.eq(MchDivisionReceiver::getMchNo, getCurrentMchNo()); + + if(queryObject.getReceiverId() != null){ + condition.eq(MchDivisionReceiver::getReceiverId, queryObject.getReceiverId()); + } + + if(StringUtils.isNotEmpty(queryObject.getReceiverAlias())){ + condition.like(MchDivisionReceiver::getReceiverAlias, queryObject.getReceiverAlias()); + } + + if(queryObject.getReceiverGroupId() != null){ + condition.eq(MchDivisionReceiver::getReceiverGroupId, queryObject.getReceiverGroupId()); + } + + if(StringUtils.isNotEmpty(queryObject.getReceiverGroupName())){ + condition.like(MchDivisionReceiver::getReceiverGroupName, queryObject.getReceiverGroupName()); + } + + if(StringUtils.isNotEmpty(queryObject.getAppId())){ + condition.like(MchDivisionReceiver::getAppId, queryObject.getAppId()); + } + + if(queryObject.getState() != null){ + condition.eq(MchDivisionReceiver::getState, queryObject.getState()); + } + + condition.orderByDesc(MchDivisionReceiver::getCreatedAt); //时间倒序 + + IPage pages = mchDivisionReceiverService.page(getIPage(true), condition); + return ApiRes.page(pages); + } + + + /** detail */ + @PreAuthorize("hasAuthority( 'ENT_DIVISION_RECEIVER_VIEW' )") + @RequestMapping(value="/{recordId}", method = RequestMethod.GET) + public ApiRes detail(@PathVariable("recordId") Long recordId) { + MchDivisionReceiver record = mchDivisionReceiverService + .getOne(MchDivisionReceiver.gw() + .eq(MchDivisionReceiver::getMchNo, getCurrentMchNo()) + .eq(MchDivisionReceiver::getReceiverId, recordId)); + if (record == null) { + throw new BizException(ApiCodeEnum.SYS_OPERATION_FAIL_SELETE); + } + return ApiRes.ok(record); + } + + /** add */ + @PreAuthorize("hasAuthority( 'ENT_DIVISION_RECEIVER_ADD' )") + @RequestMapping(value="", method = RequestMethod.POST) + @MethodLog(remark = "新增分账接收账号") + public ApiRes add() { + + DivisionReceiverBindReqModel model = getObject(DivisionReceiverBindReqModel.class); + + MchApp mchApp = mchAppService.getById(model.getAppId()); + if(mchApp == null || mchApp.getState() != CS.PUB_USABLE || !mchApp.getMchNo().equals(getCurrentMchNo()) ){ + throw new BizException("商户应用不存在或不可用"); + } + + DivisionReceiverBindRequest request = new DivisionReceiverBindRequest(); + request.setBizModel(model); + model.setMchNo(this.getCurrentMchNo()); + model.setAppId(mchApp.getAppId()); + model.setDivisionProfit(new BigDecimal(model.getDivisionProfit()).divide(new BigDecimal(100)).toPlainString()); + + JeepayClient jeepayClient = new JeepayClient(sysConfigService.getDBApplicationConfig().getPaySiteUrl(), mchApp.getAppSecret()); + + try { + DivisionReceiverBindResponse response = jeepayClient.execute(request); + if(response.getCode() != 0){ + throw new BizException(response.getMsg()); + } + return ApiRes.ok(response.get()); + } catch (JeepayException e) { + throw new BizException(e.getMessage()); + } + } + + /** update */ + @PreAuthorize("hasAuthority( 'ENT_DIVISION_RECEIVER_EDIT' )") + @RequestMapping(value="/{recordId}", method = RequestMethod.PUT) + @MethodLog(remark = "更新分账接收账号") + public ApiRes update(@PathVariable("recordId") Long recordId) { + + // 请求参数 + MchDivisionReceiver reqReceiver = getObject(MchDivisionReceiver.class); + + MchDivisionReceiver record = new MchDivisionReceiver(); + record.setReceiverAlias(reqReceiver.getReceiverAlias()); + record.setReceiverGroupId(reqReceiver.getReceiverGroupId()); + record.setState(reqReceiver.getState()); + + // 改为真实比例 + if(reqReceiver.getDivisionProfit() != null){ + record.setDivisionProfit(reqReceiver.getDivisionProfit().divide(new BigDecimal(100))); + } + + if(record.getReceiverGroupId() != null){ + MchDivisionReceiverGroup groupRecord = mchDivisionReceiverGroupService.findByIdAndMchNo(record.getReceiverGroupId(), getCurrentMchNo()); + if (record == null) { + throw new BizException("账号组不存在"); + } + record.setReceiverGroupId(groupRecord.getReceiverGroupId()); + record.setReceiverGroupName(groupRecord.getReceiverGroupName()); + } + + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(MchDivisionReceiver::getReceiverId, recordId); + updateWrapper.eq(MchDivisionReceiver::getMchNo, getCurrentMchNo()); + mchDivisionReceiverService.update(record, updateWrapper); + return ApiRes.ok(); + } + + /** delete */ + @PreAuthorize("hasAuthority('ENT_DIVISION_RECEIVER_DELETE')") + @RequestMapping(value="/{recordId}", method = RequestMethod.DELETE) + @MethodLog(remark = "删除分账接收账号") + public ApiRes del(@PathVariable("recordId") Long recordId) { + MchDivisionReceiver record = mchDivisionReceiverService.getOne(MchDivisionReceiver.gw() + .eq(MchDivisionReceiver::getReceiverGroupId, recordId).eq(MchDivisionReceiver::getMchNo, getCurrentMchNo())); + if (record == null) { + throw new BizException(ApiCodeEnum.SYS_OPERATION_FAIL_SELETE); + } + + mchDivisionReceiverService.removeById(recordId); + return ApiRes.ok(); + } + + +} diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/MchDivisionReceiverGroupController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/MchDivisionReceiverGroupController.java new file mode 100644 index 0000000..97cc267 --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/MchDivisionReceiverGroupController.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.mch.ctrl.division; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.jeequan.jeepay.core.aop.MethodLog; +import com.jeequan.jeepay.core.constants.ApiCodeEnum; +import com.jeequan.jeepay.core.constants.CS; +import com.jeequan.jeepay.core.entity.MchDivisionReceiver; +import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup; +import com.jeequan.jeepay.core.exception.BizException; +import com.jeequan.jeepay.core.model.ApiRes; +import com.jeequan.jeepay.mch.ctrl.CommonCtrl; +import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService; +import com.jeequan.jeepay.service.impl.MchDivisionReceiverService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * 商户分账接收者账号组 + * + * @author terrfly + * @site https://www.jeequan.com + * @date 2021-08-23 11:50 + */ +@RestController +@RequestMapping("api/divisionReceiverGroups") +public class MchDivisionReceiverGroupController extends CommonCtrl { + + @Autowired private MchDivisionReceiverGroupService mchDivisionReceiverGroupService; + @Autowired private MchDivisionReceiverService mchDivisionReceiverService; + + /** list */ + @PreAuthorize("hasAnyAuthority( 'ENT_DIVISION_RECEIVER_GROUP_LIST' )") + @RequestMapping(value="", method = RequestMethod.GET) + public ApiRes list() { + + MchDivisionReceiverGroup queryObject = getObject(MchDivisionReceiverGroup.class); + + LambdaQueryWrapper condition = MchDivisionReceiverGroup.gw(); + condition.eq(MchDivisionReceiverGroup::getMchNo, getCurrentMchNo()); + + if(StringUtils.isNotEmpty(queryObject.getReceiverGroupName())){ + condition.like(MchDivisionReceiverGroup::getReceiverGroupName, queryObject.getReceiverGroupName()); + } + + if(queryObject.getReceiverGroupId() != null){ + condition.eq(MchDivisionReceiverGroup::getReceiverGroupId, queryObject.getReceiverGroupId()); + } + + condition.orderByDesc(MchDivisionReceiverGroup::getCreatedAt); //时间倒序 + + IPage pages = mchDivisionReceiverGroupService.page(getIPage(true), condition); + return ApiRes.page(pages); + } + + + /** detail */ + @PreAuthorize("hasAuthority( 'ENT_DIVISION_RECEIVER_GROUP_VIEW' )") + @RequestMapping(value="/{recordId}", method = RequestMethod.GET) + public ApiRes detail(@PathVariable("recordId") Long recordId) { + MchDivisionReceiverGroup record = mchDivisionReceiverGroupService + .getOne(MchDivisionReceiverGroup.gw() + .eq(MchDivisionReceiverGroup::getMchNo, getCurrentMchNo()) + .eq(MchDivisionReceiverGroup::getReceiverGroupId, recordId)); + if (record == null) { + throw new BizException(ApiCodeEnum.SYS_OPERATION_FAIL_SELETE); + } + return ApiRes.ok(record); + } + + /** add */ + @PreAuthorize("hasAuthority( 'ENT_DIVISION_RECEIVER_GROUP_ADD' )") + @RequestMapping(value="", method = RequestMethod.POST) + @MethodLog(remark = "新增分账账号组") + public ApiRes add() { + MchDivisionReceiverGroup record = getObject(MchDivisionReceiverGroup.class); + record.setMchNo(getCurrentMchNo()); + record.setCreatedUid(getCurrentUser().getSysUser().getSysUserId()); + record.setCreatedBy(getCurrentUser().getSysUser().getRealname()); + if(mchDivisionReceiverGroupService.save(record)){ + + //更新其他组为非默认分账组 + if(record.getAutoDivisionFlag() == CS.YES){ + mchDivisionReceiverGroupService.update(new LambdaUpdateWrapper() + .set(MchDivisionReceiverGroup::getAutoDivisionFlag, CS.NO) + .eq(MchDivisionReceiverGroup::getMchNo, getCurrentMchNo()) + .ne(MchDivisionReceiverGroup::getReceiverGroupId, record.getReceiverGroupId()) + ); + } + } + return ApiRes.ok(); + } + + /** update */ + @PreAuthorize("hasAuthority( 'ENT_DIVISION_RECEIVER_GROUP_EDIT' )") + @RequestMapping(value="/{recordId}", method = RequestMethod.PUT) + @MethodLog(remark = "更新分账账号组") + public ApiRes update(@PathVariable("recordId") Long recordId) { + + MchDivisionReceiverGroup reqRecord = getObject(MchDivisionReceiverGroup.class); + + MchDivisionReceiverGroup record = new MchDivisionReceiverGroup(); + record.setReceiverGroupName(reqRecord.getReceiverGroupName()); + record.setAutoDivisionFlag(reqRecord.getAutoDivisionFlag()); + + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(MchDivisionReceiverGroup::getReceiverGroupId, recordId); + updateWrapper.eq(MchDivisionReceiverGroup::getMchNo, getCurrentMchNo()); + + if(mchDivisionReceiverGroupService.update(record, updateWrapper)){ + + //更新其他组为非默认分账组 + if(record.getAutoDivisionFlag() == CS.YES){ + mchDivisionReceiverGroupService.update(new LambdaUpdateWrapper() + .set(MchDivisionReceiverGroup::getAutoDivisionFlag, CS.NO) + .eq(MchDivisionReceiverGroup::getMchNo, getCurrentMchNo()) + .ne(MchDivisionReceiverGroup::getReceiverGroupId, recordId) + ); + } + } + + return ApiRes.ok(); + } + + /** delete */ + @PreAuthorize("hasAuthority('ENT_DIVISION_RECEIVER_GROUP_DELETE')") + @RequestMapping(value="/{recordId}", method = RequestMethod.DELETE) + @MethodLog(remark = "删除分账账号组") + public ApiRes del(@PathVariable("recordId") Long recordId) { + MchDivisionReceiverGroup record = mchDivisionReceiverGroupService.getOne(MchDivisionReceiverGroup.gw() + .eq(MchDivisionReceiverGroup::getReceiverGroupId, recordId).eq(MchDivisionReceiverGroup::getMchNo, getCurrentMchNo())); + if (record == null) { + throw new BizException(ApiCodeEnum.SYS_OPERATION_FAIL_SELETE); + } + + if( mchDivisionReceiverService.count(MchDivisionReceiver.gw().eq(MchDivisionReceiver::getReceiverGroupId, recordId)) > 0){ + throw new BizException("该组存在账号,无法删除"); + } + + mchDivisionReceiverGroupService.removeById(recordId); + return ApiRes.ok(); + } + + +} diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/PayOrderDivisionRecordController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/PayOrderDivisionRecordController.java new file mode 100644 index 0000000..ec675ed --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/division/PayOrderDivisionRecordController.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.mch.ctrl.division; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.jeequan.jeepay.core.constants.ApiCodeEnum; +import com.jeequan.jeepay.core.entity.PayOrder; +import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord; +import com.jeequan.jeepay.core.exception.BizException; +import com.jeequan.jeepay.core.model.ApiRes; +import com.jeequan.jeepay.mch.ctrl.CommonCtrl; +import com.jeequan.jeepay.service.impl.PayOrderDivisionRecordService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * 分账记录 + * + * @author terrfly + * @site https://www.jeequan.com + * @date 2021-08-25 11:50 + */ +@RestController +@RequestMapping("api/division/records") +public class PayOrderDivisionRecordController extends CommonCtrl { + + @Autowired private PayOrderDivisionRecordService payOrderDivisionRecordService; + + + /** list */ + @PreAuthorize("hasAnyAuthority( 'ENT_DIVISION_RECORD_LIST' )") + @RequestMapping(value="", method = RequestMethod.GET) + public ApiRes list() { + + PayOrderDivisionRecord queryObject = getObject(PayOrderDivisionRecord.class); + JSONObject paramJSON = getReqParamJSON(); + + LambdaQueryWrapper condition = PayOrderDivisionRecord.gw(); + condition.eq(PayOrderDivisionRecord::getMchNo, getCurrentMchNo()); + + if(queryObject.getReceiverId() != null){ + condition.eq(PayOrderDivisionRecord::getReceiverId, queryObject.getReceiverId()); + } + + if(queryObject.getReceiverGroupId() != null){ + condition.eq(PayOrderDivisionRecord::getReceiverGroupId, queryObject.getReceiverGroupId()); + } + + if(StringUtils.isNotEmpty(queryObject.getAppId())){ + condition.like(PayOrderDivisionRecord::getAppId, queryObject.getAppId()); + } + + if(queryObject.getState() != null){ + condition.eq(PayOrderDivisionRecord::getState, queryObject.getState()); + } + + if(StringUtils.isNotEmpty(queryObject.getPayOrderId())){ + condition.eq(PayOrderDivisionRecord::getPayOrderId, queryObject.getPayOrderId()); + } + + if(StringUtils.isNotEmpty(queryObject.getAccNo())){ + condition.eq(PayOrderDivisionRecord::getAccNo, queryObject.getAccNo()); + } + + if (paramJSON != null) { + if (StringUtils.isNotEmpty(paramJSON.getString("createdStart"))) { + condition.ge(PayOrderDivisionRecord::getCreatedAt, paramJSON.getString("createdStart")); + } + if (StringUtils.isNotEmpty(paramJSON.getString("createdEnd"))) { + condition.le(PayOrderDivisionRecord::getCreatedAt, paramJSON.getString("createdEnd")); + } + } + + condition.orderByDesc(PayOrderDivisionRecord::getCreatedAt); //时间倒序 + + IPage pages = payOrderDivisionRecordService.page(getIPage(true), condition); + return ApiRes.page(pages); + } + + + /** detail */ + @PreAuthorize("hasAuthority( 'ENT_DIVISION_RECORD_VIEW' )") + @RequestMapping(value="/{recordId}", method = RequestMethod.GET) + public ApiRes detail(@PathVariable("recordId") Long recordId) { + PayOrderDivisionRecord record = payOrderDivisionRecordService + .getOne(PayOrderDivisionRecord.gw() + .eq(PayOrderDivisionRecord::getMchNo, getCurrentMchNo()) + .eq(PayOrderDivisionRecord::getRecordId, recordId)); + if (record == null) { + throw new BizException(ApiCodeEnum.SYS_OPERATION_FAIL_SELETE); + } + return ApiRes.ok(record); + } + +} diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/merchant/MchPayInterfaceConfigController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/merchant/MchPayInterfaceConfigController.java index ce59f95..7fff456 100644 --- a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/merchant/MchPayInterfaceConfigController.java +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/merchant/MchPayInterfaceConfigController.java @@ -21,26 +21,22 @@ import com.jeequan.jeepay.components.mq.vender.IMQSender; import com.jeequan.jeepay.core.aop.MethodLog; import com.jeequan.jeepay.core.constants.ApiCodeEnum; import com.jeequan.jeepay.core.constants.CS; -import com.jeequan.jeepay.core.entity.MchApp; -import com.jeequan.jeepay.core.entity.MchInfo; -import com.jeequan.jeepay.core.entity.PayInterfaceConfig; -import com.jeequan.jeepay.core.entity.PayInterfaceDefine; +import com.jeequan.jeepay.core.entity.*; import com.jeequan.jeepay.core.exception.BizException; import com.jeequan.jeepay.core.model.ApiRes; import com.jeequan.jeepay.core.model.params.NormalMchParams; import com.jeequan.jeepay.core.utils.StringKit; import com.jeequan.jeepay.mch.ctrl.CommonCtrl; -import com.jeequan.jeepay.service.impl.MchAppService; -import com.jeequan.jeepay.service.impl.MchInfoService; -import com.jeequan.jeepay.service.impl.PayInterfaceConfigService; -import com.jeequan.jeepay.service.impl.SysConfigService; +import com.jeequan.jeepay.service.impl.*; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.math.BigDecimal; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * 商户支付接口配置类 @@ -180,4 +176,25 @@ public class MchPayInterfaceConfigController extends CommonCtrl { } + /** 查询当前应用支持的支付接口 */ + @PreAuthorize("hasAuthority( 'ENT_DIVISION_RECEIVER_ADD' )") + @RequestMapping(value="ifCodes/{appId}", method = RequestMethod.GET) + public ApiRes getIfCodeByAppId(@PathVariable("appId") String appId) { + + if(mchAppService.count(MchApp.gw().eq(MchApp::getMchNo, getCurrentMchNo()).eq(MchApp::getAppId, appId)) <= 0){ + throw new BizException("商户应用不存在"); + } + + Set result = new HashSet<>(); + + payInterfaceConfigService.list(PayInterfaceConfig.gw().select(PayInterfaceConfig::getIfCode) + .eq(PayInterfaceConfig::getState, CS.PUB_USABLE) + .eq(PayInterfaceConfig::getInfoId, appId) + .eq(PayInterfaceConfig::getInfoType, CS.INFO_TYPE_MCH_APP) + ).stream().forEach(r -> result.add(r.getIfCode())); + + return ApiRes.ok(result); + } + + } diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/order/PayOrderController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/order/PayOrderController.java index 2a878f6..7e4a1b8 100644 --- a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/order/PayOrderController.java +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/order/PayOrderController.java @@ -85,6 +85,9 @@ public class PayOrderController extends CommonCtrl { if (StringUtils.isNotEmpty(payOrder.getAppId())) { wrapper.eq(PayOrder::getAppId, payOrder.getAppId()); } + if (payOrder.getDivisionState() != null) { + wrapper.eq(PayOrder::getDivisionState, payOrder.getDivisionState()); + } if (paramJSON != null) { if (StringUtils.isNotEmpty(paramJSON.getString("createdStart"))) { wrapper.ge(PayOrder::getCreatedAt, paramJSON.getString("createdStart")); diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java index 4859a2d..b41fe30 100644 --- a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java @@ -81,6 +81,13 @@ public class PaytestController extends CommonCtrl { String mchOrderNo = getValStringRequired("mchOrderNo"); String wayCode = getValStringRequired("wayCode"); + Byte divisionMode = getValByteRequired("divisionMode"); + String orderTitle = getValStringRequired("orderTitle"); + + if(StringUtils.isEmpty(orderTitle)){ + throw new BizException("订单标题不能为空"); + } + // 前端明确了支付参数的类型 payDataType String payDataType = getValString("payDataType"); String authCode = getValString("authCode"); @@ -102,9 +109,10 @@ public class PaytestController extends CommonCtrl { model.setAmount(amount); model.setCurrency("CNY"); model.setClientIp(getClientIp()); - model.setSubject(getCurrentMchNo() + "商户联调"); - model.setBody(getCurrentMchNo() + "商户联调"); + model.setSubject(orderTitle + "[" + getCurrentMchNo() + "商户联调]"); + model.setBody(orderTitle + "[" + getCurrentMchNo() + "商户联调]"); model.setNotifyUrl(sysConfigService.getDBApplicationConfig().getMchSiteUrl() + "/api/anon/paytestNotify/payOrder"); //回调地址 + model.setDivisionMode(divisionMode); //分账模式 //设置扩展参数 JSONObject extParams = new JSONObject(); diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/AbstractPaymentService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/AbstractPaymentService.java index 8f83b9b..bef6a5c 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/AbstractPaymentService.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/AbstractPaymentService.java @@ -16,6 +16,7 @@ package com.jeequan.jeepay.pay.channel; +import com.jeequan.jeepay.core.entity.PayOrder; import com.jeequan.jeepay.pay.util.ChannelCertConfigKitBean; import com.jeequan.jeepay.service.impl.SysConfigService; import org.springframework.beans.factory.annotation.Autowired; @@ -32,6 +33,15 @@ public abstract class AbstractPaymentService implements IPaymentService{ @Autowired protected SysConfigService sysConfigService; @Autowired protected ChannelCertConfigKitBean channelCertConfigKitBean; + /** 订单分账(一般用作 如微信订单将在下单处做标记) */ + protected boolean isDivisionOrder(PayOrder payOrder){ + //订单分账, 将冻结商户资金。 + if(payOrder.getDivisionMode() != null && (PayOrder.DIVISION_MODE_AUTO == payOrder.getDivisionMode() || PayOrder.DIVISION_MODE_MANUAL == payOrder.getDivisionMode() )){ + return true; + } + return false; + } + protected String getNotifyUrl(){ return sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/api/pay/notify/" + getIfCode(); } diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IDivisionService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IDivisionService.java index d207b83..d4d6d67 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IDivisionService.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IDivisionService.java @@ -16,6 +16,7 @@ package com.jeequan.jeepay.pay.channel; 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.entity.TransferOrder; import com.jeequan.jeepay.pay.model.MchAppConfigContext; @@ -40,9 +41,9 @@ public interface IDivisionService { boolean isSupport(); /** 绑定关系 **/ - boolean bind(MchDivisionReceiver mchDivisionReceiver, MchAppConfigContext mchAppConfigContext); + ChannelRetMsg bind(MchDivisionReceiver mchDivisionReceiver, MchAppConfigContext mchAppConfigContext); /** 单次分账 (无需调用完结接口,或自动解冻商户资金) **/ - boolean singleDivision(List recordList, MchAppConfigContext mchAppConfigContext); + ChannelRetMsg singleDivision(PayOrder payOrder, List recordList, MchAppConfigContext mchAppConfigContext); } diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayDivisionService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayDivisionService.java new file mode 100644 index 0000000..6367714 --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayDivisionService.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.alipay; + +import com.alibaba.fastjson.JSON; +import com.alipay.api.domain.*; +import com.alipay.api.request.AlipayTradeOrderSettleRequest; +import com.alipay.api.request.AlipayTradeRoyaltyRelationBindRequest; +import com.alipay.api.response.AlipayTradeOrderSettleResponse; +import com.alipay.api.response.AlipayTradeRoyaltyRelationBindResponse; +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.utils.AmountUtil; +import com.jeequan.jeepay.core.utils.RegKit; +import com.jeequan.jeepay.core.utils.SeqKit; +import com.jeequan.jeepay.pay.channel.IDivisionService; +import com.jeequan.jeepay.pay.exception.ChannelException; +import com.jeequan.jeepay.pay.model.MchAppConfigContext; +import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** +* 分账接口: 支付宝官方 +* +* @author terrfly +* @site https://www.jeepay.vip +* @date 2021/8/22 09:05 +*/ +@Slf4j +@Service +public class AlipayDivisionService implements IDivisionService { + + @Override + public String getIfCode() { + return CS.IF_CODE.ALIPAY; + } + + @Override + public boolean isSupport() { + return false; + } + + @Override + public ChannelRetMsg bind(MchDivisionReceiver mchDivisionReceiver, MchAppConfigContext mchAppConfigContext) { + + + try { + AlipayTradeRoyaltyRelationBindRequest request = new AlipayTradeRoyaltyRelationBindRequest(); + AlipayTradeRoyaltyRelationBindModel model = new AlipayTradeRoyaltyRelationBindModel(); + request.setBizModel(model); + model.setOutRequestNo(SeqKit.genDivisionBatchId()); + + RoyaltyEntity royaltyEntity = new RoyaltyEntity(); + + royaltyEntity.setType("loginName"); + if(RegKit.isAlipayUserId(mchDivisionReceiver.getAccNo())){ + royaltyEntity.setType("userId"); + } + royaltyEntity.setAccount(mchDivisionReceiver.getAccNo()); + royaltyEntity.setName(mchDivisionReceiver.getAccName()); + royaltyEntity.setMemo(mchDivisionReceiver.getRelationTypeName()); //分账关系描述 + model.setReceiverList(Arrays.asList(royaltyEntity)); + + AlipayTradeRoyaltyRelationBindResponse alipayResp = mchAppConfigContext.getAlipayClientWrapper().execute(request); + + if(alipayResp.isSuccess()){ + return ChannelRetMsg.confirmSuccess(null); + } + + //异常: + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + channelRetMsg.setChannelErrCode(AlipayKit.appendErrCode(alipayResp.getCode(), alipayResp.getSubCode())); + channelRetMsg.setChannelErrMsg(AlipayKit.appendErrMsg(alipayResp.getMsg(), alipayResp.getSubMsg())); + return channelRetMsg; + + } catch (ChannelException e) { + + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + channelRetMsg.setChannelErrCode(e.getChannelRetMsg().getChannelErrCode()); + channelRetMsg.setChannelErrMsg(e.getChannelRetMsg().getChannelErrMsg()); + return channelRetMsg; + + } catch (Exception e) { + log.error("绑定支付宝账号异常", e); + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + channelRetMsg.setChannelErrMsg(e.getMessage()); + return channelRetMsg; + } + } + + @Override + public ChannelRetMsg singleDivision(PayOrder payOrder, List recordList, MchAppConfigContext mchAppConfigContext) { + + + try { + + AlipayTradeOrderSettleRequest request = new AlipayTradeOrderSettleRequest(); + AlipayTradeOrderSettleModel model = new AlipayTradeOrderSettleModel(); + request.setBizModel(model); + + model.setOutRequestNo(recordList.get(0).getBatchOrderId()); //结算请求流水号,由商家自定义。32个字符以内,仅可包含字母、数字、下划线。需保证在商户端不重复。 + model.setTradeNo(recordList.get(0).getPayOrderChannelOrderNo()); //支付宝订单号 + + List reqReceiverList = new ArrayList<>(); + + for (int i = 0; i < recordList.size(); i++) { + + PayOrderDivisionRecord record = recordList.get(i); + + if(record.getCalDivisionAmount() <= 0){ //金额为 0 不参与分账处理 + continue; + } + + OpenApiRoyaltyDetailInfoPojo reqReceiver = new OpenApiRoyaltyDetailInfoPojo(); + reqReceiver.setRoyaltyType("transfer"); //分账类型: 普通分账 + + // 出款信息 + // reqReceiver.setTransOutType("loginName"); reqReceiver.setTransOut("xqxemt4735@sandbox.com"); + + // 入款信息 + reqReceiver.setTransIn(record.getAccNo()); //收入方账号 + reqReceiver.setTransInType("loginName"); + if(RegKit.isAlipayUserId(record.getAccNo())){ + reqReceiver.setTransInType("userId"); + } + // 分账金额 + reqReceiver.setAmount(AmountUtil.convertCent2Dollar(record.getCalDivisionAmount())); + reqReceiver.setDesc("[" + payOrder.getPayOrderId() + "]订单分账"); + reqReceiverList.add(reqReceiver); + + } + + if(reqReceiverList.isEmpty()){ // 当无分账用户时, 支付宝不允许发起分账请求, 支付宝没有完结解决,直接响应成功即可。 + return ChannelRetMsg.confirmSuccess(null); + } + + model.setRoyaltyParameters(reqReceiverList); // 分账明细信息 + + // 完结 + SettleExtendParams settleExtendParams = new SettleExtendParams(); + settleExtendParams.setRoyaltyFinish("true"); + model.setExtendParams(settleExtendParams); + + //调起支付宝分账接口 + if(log.isInfoEnabled()){ + log.info("订单:[{}], 支付宝分账请求:{}", payOrder.getPayOrderId(), JSON.toJSONString(model)); + } + AlipayTradeOrderSettleResponse alipayResp = mchAppConfigContext.getAlipayClientWrapper().execute(request); + log.info("订单:[{}], 支付宝分账响应:{}", payOrder.getPayOrderId(), alipayResp.getBody()); + if(alipayResp.isSuccess()){ + return ChannelRetMsg.confirmSuccess(alipayResp.getTradeNo()); + } + + //异常: + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + channelRetMsg.setChannelErrCode(AlipayKit.appendErrCode(alipayResp.getCode(), alipayResp.getSubCode())); + channelRetMsg.setChannelErrMsg(AlipayKit.appendErrMsg(alipayResp.getMsg(), alipayResp.getSubMsg())); + return channelRetMsg; + + } catch (ChannelException e) { + + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + channelRetMsg.setChannelErrCode(e.getChannelRetMsg().getChannelErrCode()); + channelRetMsg.setChannelErrMsg(e.getChannelRetMsg().getChannelErrMsg()); + return channelRetMsg; + + } catch (Exception e) { + log.error("绑定支付宝账号异常", e); + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + channelRetMsg.setChannelErrMsg(e.getMessage()); + return channelRetMsg; + } + } + +} diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/ctrl/AlipayBizController.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/ctrl/AlipayBizController.java index d92c299..19244c1 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/ctrl/AlipayBizController.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/ctrl/AlipayBizController.java @@ -83,7 +83,7 @@ public class AlipayBizController extends AbstractCtrl { } String redirectUrl = sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/api/channelbiz/alipay/appToAppAuthCallback"; - response.sendRedirect(String.format(oauthUrl, alipayIsvParams.getAppId(), URLUtil.encode(redirectUrl), isvAndMchAppId)); + response.sendRedirect(String.format(oauthUrl, alipayIsvParams.getAppId(), URLUtil.encodeAll(redirectUrl), isvAndMchAppId)); } /** 支付宝授权回调地址 **/ diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayDivisionService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayDivisionService.java index 5dcfd34..45184e1 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayDivisionService.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayDivisionService.java @@ -17,19 +17,21 @@ 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.bean.profitsharing.*; 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.PayOrder; import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord; +import com.jeequan.jeepay.core.utils.SeqKit; import com.jeequan.jeepay.pay.channel.IDivisionService; +import com.jeequan.jeepay.pay.channel.wxpay.kits.WxpayKit; import com.jeequan.jeepay.pay.model.MchAppConfigContext; +import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.math.BigDecimal; import java.util.List; /** @@ -54,12 +56,15 @@ public class WxpayDivisionService implements IDivisionService { } @Override - public boolean bind(MchDivisionReceiver mchDivisionReceiver, MchAppConfigContext mchAppConfigContext) { + public ChannelRetMsg bind(MchDivisionReceiver mchDivisionReceiver, MchAppConfigContext mchAppConfigContext) { try { ProfitSharingReceiverRequest request = new ProfitSharingReceiverRequest(); + //放置isv信息 + WxpayKit.putApiIsvInfo(mchAppConfigContext, request); + JSONObject receiverJSON = new JSONObject(); // 0-个人, 1-商户 (目前仅支持服务商appI获取个人openId, 即: PERSONAL_OPENID, 不支持 PERSONAL_SUB_OPENID ) @@ -73,30 +78,49 @@ public class WxpayDivisionService implements IDivisionService { ProfitSharingReceiverResult profitSharingReceiverResult = mchAppConfigContext.getWxServiceWrapper().getWxPayService().getProfitSharingService().addReceiver(request); + // 明确成功 + return ChannelRetMsg.confirmSuccess(null); + } catch (WxPayException wxPayException) { - wxPayException.printStackTrace(); + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + WxpayKit.commonSetErrInfo(channelRetMsg, wxPayException); + return channelRetMsg; + + } catch (Exception e) { + + log.error("请求微信绑定分账接口异常!", e); + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + channelRetMsg.setChannelErrMsg("系统异常:" + e.getMessage()); + return channelRetMsg; } - - - return false; } @Override - public boolean singleDivision(List recordList, MchAppConfigContext mchAppConfigContext) { + public ChannelRetMsg singleDivision(PayOrder payOrder, List 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()); + request.setTransactionId(payOrder.getChannelOrderNo()); + + //放置isv信息 + WxpayKit.putApiIsvInfo(mchAppConfigContext, request); + + if(recordList.isEmpty()){ + request.setOutOrderNo(SeqKit.genDivisionBatchId()); // 随机生成一个订单号 + }else{ + request.setOutOrderNo(recordList.get(0).getBatchOrderId()); //取到批次号 + } JSONArray receiverJSONArray = new JSONArray(); - for (PayOrderDivisionRecord record : recordList) { + for (int i = 0; i < recordList.size(); i++) { + + PayOrderDivisionRecord record = recordList.get(i); + if(record.getCalDivisionAmount() <= 0){ + continue; + } + JSONObject receiverJSON = new JSONObject(); // 0-个人, 1-商户 (目前仅支持服务商appI获取个人openId, 即: PERSONAL_OPENID, 不支持 PERSONAL_SUB_OPENID ) receiverJSON.put("type", record.getAccType() == 0 ? "PERSONAL_OPENID" : "MERCHANT_ID"); @@ -106,15 +130,45 @@ public class WxpayDivisionService implements IDivisionService { receiverJSONArray.add(receiverJSON); } + //不存在接收账号时,订单完结(解除冻结金额) + if(receiverJSONArray.isEmpty()){ + return ChannelRetMsg.confirmSuccess(this.divisionFinish(payOrder, mchAppConfigContext)); + } + request.setReceivers(receiverJSONArray.toJSONString()); ProfitSharingResult profitSharingResult = mchAppConfigContext.getWxServiceWrapper().getWxPayService().getProfitSharingService().profitSharing(request); + return ChannelRetMsg.confirmSuccess(profitSharingResult.getOrderId()); + + } catch (WxPayException wxPayException) { + + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + WxpayKit.commonSetErrInfo(channelRetMsg, wxPayException); + return channelRetMsg; } catch (Exception e) { - e.printStackTrace(); + log.error("微信分账失败", e); + ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmFail(); + channelRetMsg.setChannelErrMsg(e.getMessage()); + return channelRetMsg; } - - - return false; } + + + /** 调用订单的完结接口 (分账对象不存在时) */ + private String divisionFinish(PayOrder payOrder,MchAppConfigContext mchAppConfigContext) throws WxPayException { + + ProfitSharingFinishRequest request = new ProfitSharingFinishRequest(); + + //放置isv信息 + WxpayKit.putApiIsvInfo(mchAppConfigContext, request); + + request.setSubAppId(null); // 传入subAppId 将导致签名失败 + + request.setTransactionId(payOrder.getChannelOrderNo()); + request.setOutOrderNo(SeqKit.genDivisionBatchId()); + request.setDescription("完结分账"); + return mchAppConfigContext.getWxServiceWrapper().getWxPayService().getProfitSharingService().profitSharingFinish(request).getOrderId(); + } + } diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayPaymentService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayPaymentService.java index cac2935..25e6aa7 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayPaymentService.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayPaymentService.java @@ -90,6 +90,11 @@ public class WxpayPaymentService extends AbstractPaymentService { request.setNotifyUrl(getNotifyUrl()); request.setProductId(System.currentTimeMillis()+""); + //订单分账, 将冻结商户资金。 + if(isDivisionOrder(payOrder)){ + request.setProfitSharing("Y"); + } + // 特约商户 if(mchAppConfigContext.isIsvsubMch()){ WxpayIsvsubMchParams isvsubMchParams = mchAppConfigContext.getIsvsubMchParamsByIfCode(getIfCode(), WxpayIsvsubMchParams.class); @@ -125,6 +130,13 @@ public class WxpayPaymentService extends AbstractPaymentService { sceneInfo.put("payer_client_ip", payOrder.getClientIp()); reqJSON.put("scene_info", sceneInfo); + //订单分账, 将冻结商户资金。 + if(isDivisionOrder(payOrder)){ + JSONObject settleInfo = new JSONObject(); + settleInfo.put("profit_sharing", true); + reqJSON.put("settle_info", settleInfo); + } + WxPayService wxPayService = mchAppConfigContext.getWxServiceWrapper().getWxPayService(); if(mchAppConfigContext.isIsvsubMch()){ // 特约商户 WxpayIsvsubMchParams isvsubMchParams = mchAppConfigContext.getIsvsubMchParamsByIfCode(getIfCode(), WxpayIsvsubMchParams.class); diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/payway/WxBar.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/payway/WxBar.java index b45d2c3..2b478e6 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/payway/WxBar.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/payway/WxBar.java @@ -70,6 +70,11 @@ public class WxBar extends WxpayPaymentService { request.setSpbillCreateIp(payOrder.getClientIp()); request.setAuthCode(bizRQ.getAuthCode().trim()); + //订单分账, 将冻结商户资金。 + if(isDivisionOrder(payOrder)){ + request.setProfitSharing("Y"); + } + //放置isv信息 WxpayKit.putApiIsvInfo(mchAppConfigContext, request); diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/division/MchDivisionReceiverBindController.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/division/MchDivisionReceiverBindController.java new file mode 100644 index 0000000..3d12643 --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/division/MchDivisionReceiverBindController.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jeequan.jeepay.pay.ctrl.division; + +import com.jeequan.jeepay.core.constants.CS; +import com.jeequan.jeepay.core.entity.MchDivisionReceiver; +import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup; +import com.jeequan.jeepay.core.entity.MchInfo; +import com.jeequan.jeepay.core.exception.BizException; +import com.jeequan.jeepay.core.model.ApiRes; +import com.jeequan.jeepay.core.utils.SpringBeansUtil; +import com.jeequan.jeepay.pay.channel.IDivisionService; +import com.jeequan.jeepay.pay.ctrl.ApiController; +import com.jeequan.jeepay.pay.model.MchAppConfigContext; +import com.jeequan.jeepay.pay.rqrs.division.DivisionReceiverBindRQ; +import com.jeequan.jeepay.pay.rqrs.division.DivisionReceiverBindRS; +import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg; +import com.jeequan.jeepay.pay.rqrs.transfer.TransferOrderRS; +import com.jeequan.jeepay.pay.service.ConfigContextService; +import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService; +import com.jeequan.jeepay.service.impl.MchDivisionReceiverService; +import com.jeequan.jeepay.service.impl.PayInterfaceConfigService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.math.BigDecimal; +import java.util.Date; + +/** +* 分账账号绑定 +* +* @author terrfly +* @site https://www.jeepay.vip +* @date 2021/8/25 9:07 +*/ +@Slf4j +@RestController +public class MchDivisionReceiverBindController extends ApiController { + + @Autowired private ConfigContextService configContextService; + @Autowired private PayInterfaceConfigService payInterfaceConfigService; + @Autowired private MchDivisionReceiverService mchDivisionReceiverService; + @Autowired private MchDivisionReceiverGroupService mchDivisionReceiverGroupService; + + /** 分账账号绑定 **/ + @PostMapping("/api/division/receiver/bind") + public ApiRes bind(){ + + //获取参数 & 验签 + DivisionReceiverBindRQ bizRQ = getRQByWithMchSign(DivisionReceiverBindRQ.class); + + try { + + //检查商户应用是否存在该接口 + String ifCode = bizRQ.getIfCode(); + + + // 商户配置信息 + MchAppConfigContext mchAppConfigContext = configContextService.getMchAppConfigContext(bizRQ.getMchNo(), bizRQ.getAppId()); + if(mchAppConfigContext == null){ + throw new BizException("获取商户应用信息失败"); + } + + MchInfo mchInfo = mchAppConfigContext.getMchInfo(); + + if(!payInterfaceConfigService.mchAppHasAvailableIfCode(bizRQ.getAppId(), ifCode)){ + throw new BizException("商户应用的支付配置不存在或已关闭"); + } + + MchDivisionReceiverGroup group = mchDivisionReceiverGroupService.findByIdAndMchNo(bizRQ.getReceiverGroupId(), bizRQ.getMchNo()); + if(group == null){ + throw new BizException("商户分账账号组不存在,请检查或进入商户平台进行创建操作"); + } + + BigDecimal divisionProfit = new BigDecimal(bizRQ.getDivisionProfit()); + if(divisionProfit.compareTo(BigDecimal.ZERO) <= 0 || divisionProfit.compareTo(BigDecimal.ONE) > 1){ + throw new BizException("账号分账比例有误, 配置值为[0.0001~1.0000]"); + } + + + //生成数据库对象信息 (数据不完成, 暂时不可入库操作) + MchDivisionReceiver receiver = genRecord(bizRQ, group, mchInfo, divisionProfit); + + //调起上游接口 + + IDivisionService divisionService = SpringBeansUtil.getBean(ifCode + "DivisionService", IDivisionService.class); + if(divisionService == null){ + throw new BizException("系统不支持该分账接口"); + } + + + ChannelRetMsg retMsg = divisionService.bind(receiver, mchAppConfigContext); + if(retMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){ + + receiver.setState(CS.YES); + receiver.setBindSuccessTime(new Date()); + mchDivisionReceiverService.save(receiver); + + }else{ + + receiver.setState(CS.NO); + receiver.setChannelBindResult(retMsg.getChannelErrMsg()); + } + + DivisionReceiverBindRS bizRes = DivisionReceiverBindRS.buildByRecord(receiver); + + if(retMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){ + + bizRes.setBindState(CS.YES); + + }else{ + bizRes.setBindState(CS.NO); + bizRes.setErrCode(retMsg.getChannelErrCode()); + bizRes.setErrMsg(retMsg.getChannelErrMsg()); + } + + return ApiRes.okWithSign(bizRes, mchAppConfigContext.getMchApp().getAppSecret()); + + } catch (BizException e) { + return ApiRes.customFail(e.getMessage()); + + } catch (Exception e) { + log.error("系统异常:{}", e); + return ApiRes.customFail("系统异常"); + } + } + + private MchDivisionReceiver genRecord(DivisionReceiverBindRQ bizRQ, MchDivisionReceiverGroup group, MchInfo mchInfo, BigDecimal divisionProfit){ + + MchDivisionReceiver receiver = new MchDivisionReceiver(); + receiver.setReceiverAlias(StringUtils.defaultIfEmpty(bizRQ.getReceiverAlias(), bizRQ.getAccNo())); //别名 + receiver.setReceiverGroupId(bizRQ.getReceiverGroupId()); //分组ID + receiver.setReceiverGroupName(group.getReceiverGroupName()); //组名称 + receiver.setMchNo(bizRQ.getMchNo()); //商户号 + receiver.setIsvNo(mchInfo.getIsvNo()); //isvNo + receiver.setAppId(bizRQ.getAppId()); //appId + receiver.setIfCode(bizRQ.getIfCode()); //接口代码 + receiver.setAccType(bizRQ.getAccType()); //账号类型 + receiver.setAccNo(bizRQ.getAccNo()); //账号 + receiver.setAccName(bizRQ.getAccName()); //账号名称 + receiver.setRelationType(bizRQ.getRelationType()); //关系 + + receiver.setRelationTypeName(getRelationTypeName(bizRQ.getRelationType())); //关系名称 + + if(receiver.getRelationTypeName() == null){ + receiver.setRelationTypeName(bizRQ.getRelationTypeName()); + } + + receiver.setDivisionProfit(divisionProfit); //分账比例 + receiver.setChannelExtInfo(bizRQ.getChannelExtInfo()); //渠道信息 + + return receiver; + } + + public String getRelationTypeName(String relationType){ + + if("PARTNER".equals(relationType)){ + return "合作伙伴"; + }else if("SERVICE_PROVIDER".equals(relationType)){ + return "服务商"; + }else if("STORE".equals(relationType)){ + return "门店"; + }else if("STAFF".equals(relationType)){ + return "员工"; + }else if("STORE_OWNER".equals(relationType)){ + return "店主"; + }else if("HEADQUARTER".equals(relationType)){ + return "总部"; + }else if("BRAND".equals(relationType)){ + return "品牌方"; + }else if("DISTRIBUTOR".equals(relationType)){ + return "分销商"; + }else if("USER".equals(relationType)){ + return "用户"; + }else if("SUPPLIER".equals(relationType)){ + return "供应商"; + } + return null; + } + + + +} diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java index 3ddad30..e72d0b6 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java @@ -234,7 +234,6 @@ public abstract class AbstractPayOrderController extends ApiController { } payOrder.setMchFeeAmount(AmountUtil.calPercentageFee(payOrder.getAmount(), payOrder.getMchFeeRate())); //商户手续费,单位分 - payOrder.setMchIncomeAmount(payOrder.getAmount() - payOrder.getMchFeeAmount()); //商户入账金额(支付金额-手续费),单位分 payOrder.setCurrency(rq.getCurrency()); //币种 payOrder.setState(PayOrder.STATE_INIT); //订单状态, 默认订单生成状态 diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/qr/ChannelUserIdController.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/qr/ChannelUserIdController.java index 03c02ec..3529944 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/qr/ChannelUserIdController.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/qr/ChannelUserIdController.java @@ -20,7 +20,6 @@ import com.alibaba.fastjson.JSONObject; import com.jeequan.jeepay.core.constants.CS; import com.jeequan.jeepay.core.exception.BizException; import com.jeequan.jeepay.core.utils.JeepayKit; -import com.jeequan.jeepay.core.utils.JsonKit; import com.jeequan.jeepay.core.utils.SpringBeansUtil; import com.jeequan.jeepay.core.utils.StringKit; import com.jeequan.jeepay.pay.channel.IChannelUserService; diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/model/AlipayClientWrapper.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/model/AlipayClientWrapper.java index ee5e356..0afdc53 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/model/AlipayClientWrapper.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/model/AlipayClientWrapper.java @@ -58,8 +58,6 @@ public class AlipayClientWrapper { alipayResp = alipayClient.execute(request); } - //判断返回的值: // TODO - return alipayResp; } catch (AlipayApiException e) { // 调起接口前出现异常,如私钥问题。 调起后出现验签异常等。 diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/PayOrderDivisionMQReceiver.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/PayOrderDivisionMQReceiver.java index f66fce0..9d7b374 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/PayOrderDivisionMQReceiver.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/PayOrderDivisionMQReceiver.java @@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ; import com.jeequan.jeepay.core.constants.CS; import com.jeequan.jeepay.core.entity.MchDivisionReceiver; +import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup; import com.jeequan.jeepay.core.entity.PayOrder; import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord; import com.jeequan.jeepay.core.exception.BizException; @@ -31,7 +32,9 @@ import com.jeequan.jeepay.core.utils.SpringBeansUtil; import com.jeequan.jeepay.pay.channel.IDivisionService; import com.jeequan.jeepay.pay.channel.ITransferService; import com.jeequan.jeepay.pay.model.MchAppConfigContext; +import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg; import com.jeequan.jeepay.pay.service.ConfigContextService; +import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService; import com.jeequan.jeepay.service.impl.MchDivisionReceiverService; import com.jeequan.jeepay.service.impl.PayOrderDivisionRecordService; import com.jeequan.jeepay.service.impl.PayOrderService; @@ -41,6 +44,7 @@ import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Date; import java.util.List; /** @@ -59,6 +63,8 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive @Autowired private MchDivisionReceiverService mchDivisionReceiverService; @Autowired + private MchDivisionReceiverGroupService mchDivisionReceiverGroupService; + @Autowired private PayOrderDivisionRecordService payOrderDivisionRecordService; @Autowired private ConfigContextService configContextService; @@ -95,8 +101,8 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive return ; } - // 查询所有的分账接收对象 - List allReceiver = queryReceiver(payOrder, payload.getReceiverList()); + // 查询&过滤 所有的分账接收对象 + List allReceiver = this.queryReceiver(payOrder, payload.getReceiverList()); //得到全部分账比例 (所有待分账账号的分账比例总和) BigDecimal allDivisionProfit = BigDecimal.ZERO; @@ -104,8 +110,11 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive allDivisionProfit = allDivisionProfit.add(receiver.getDivisionProfit()); } - //剩余待分账金额 (用作最后一个分账账号的 计算, 避免出现分账金额超出最大) - Long subDivisionAmount = AmountUtil.calPercentageFee(payOrder.getMchIncomeAmount(), allDivisionProfit); + //计算分账金额 = 商家实际入账金额 + Long payOrderDivisionAmount = payOrderService.calMchIncomeAmount(payOrder); + + //剩余待分账金额 (用作最后一个分账账号的 计算, 避免出现分账金额超出最大) [结果向下取整 , 避免出现金额溢出的情况。 ] + Long subDivisionAmount = AmountUtil.calPercentageFee(payOrderDivisionAmount, allDivisionProfit, BigDecimal.ROUND_FLOOR); List recordList = new ArrayList<>(); @@ -115,7 +124,7 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive for (MchDivisionReceiver receiver : allReceiver) { - PayOrderDivisionRecord record = genRecord(batchOrderId, payOrder, receiver, subDivisionAmount); + PayOrderDivisionRecord record = genRecord(batchOrderId, payOrder, receiver, payOrderDivisionAmount, subDivisionAmount); //剩余金额 subDivisionAmount = subDivisionAmount - record.getCalDivisionAmount(); @@ -134,14 +143,19 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive throw new BizException("通道无此分账接口"); } - divisionService.singleDivision(recordList, configContextService.getMchAppConfigContext(payOrder.getMchNo(), payOrder.getAppId())); + ChannelRetMsg channelRetMsg = divisionService.singleDivision(payOrder, recordList, configContextService.getMchAppConfigContext(payOrder.getMchNo(), payOrder.getAppId())); - if(true) { + // 确认分账成功 + if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) { //分账成功 + payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_SUCCESS, + channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelOriginResponse()); }else{ //分账失败 + payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_FAIL, + channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelErrMsg()); } } catch (BizException e) { @@ -149,14 +163,14 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive } catch (Exception e) { log.error("{}, 调用分账接口异常", logPrefix, e); - - //分账失败 - + payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_FAIL, + null, "系统异常:" + e.getMessage()); } //更新 支付订单主表状态 分账任务已结束。 payOrderService.update(new LambdaUpdateWrapper() .set(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_FINISH) + .set(PayOrder::getDivisionLastTime, new Date()) .eq(PayOrder::getPayOrderId, payload.getPayOrderId()) .eq(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_ING) ); @@ -168,7 +182,8 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive /** 生成对象信息 **/ - private PayOrderDivisionRecord genRecord(String batchOrderId, PayOrder payOrder, MchDivisionReceiver receiver, Long subDivisionAmount){ + private PayOrderDivisionRecord genRecord(String batchOrderId, PayOrder payOrder, MchDivisionReceiver receiver, + Long payOrderDivisionAmount, Long subDivisionAmount){ PayOrderDivisionRecord record = new PayOrderDivisionRecord(); record.setMchNo(payOrder.getMchNo()); @@ -180,11 +195,12 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive record.setPayOrderId(payOrder.getPayOrderId()); record.setPayOrderChannelOrderNo(payOrder.getChannelOrderNo()); //支付订单渠道订单号 record.setPayOrderAmount(payOrder.getAmount()); //订单金额 - record.setPayOrderDivisionAmount(payOrder.getMchIncomeAmount()); // 订单实际分账金额, 单位:分(订单金额 - 商户手续费 - 已退款金额) //TODO 实际计算金额 + record.setPayOrderDivisionAmount(payOrderDivisionAmount); // 订单计算分账金额 record.setBatchOrderId(batchOrderId); //系统分账批次号 - record.setState(MchDivisionReceiver.STATE_WAIT); //状态: 待分账 + record.setState(PayOrderDivisionRecord.STATE_WAIT); //状态: 待分账 record.setReceiverId(receiver.getReceiverId()); record.setReceiverGroupId(receiver.getReceiverGroupId()); + record.setReceiverAlias(receiver.getReceiverAlias()); record.setAccType(receiver.getAccType()); record.setAccNo(receiver.getAccNo()); record.setAccName(receiver.getAccName()); @@ -195,7 +211,12 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive if( subDivisionAmount <= 0 ) { record.setCalDivisionAmount(0L); }else{ + + //计算的分账金额 record.setCalDivisionAmount(AmountUtil.calPercentageFee(record.getPayOrderDivisionAmount(), record.getDivisionProfit())); + if(record.getCalDivisionAmount() > subDivisionAmount){ // 分账金额超过剩余总金额时: 将按照剩余金额进行分账。 + record.setCalDivisionAmount(subDivisionAmount); + } } return record; @@ -212,6 +233,20 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive queryWrapper.eq(MchDivisionReceiver::getIfCode, payOrder.getIfCode()); //ifCode queryWrapper.eq(MchDivisionReceiver::getState, CS.PUB_USABLE); // 可用状态的账号 + // 未设置接收者账号信息, 需要查询 自动分账组的账号 + if(customerDivisionReceiverList == null){ + + List groups = mchDivisionReceiverGroupService.list( + MchDivisionReceiverGroup.gw().eq(MchDivisionReceiverGroup::getMchNo, payOrder.getMchNo()) + .eq(MchDivisionReceiverGroup::getAutoDivisionFlag, CS.YES)); + + if(groups.isEmpty()){ + return new ArrayList<>(); + } + + queryWrapper.eq(MchDivisionReceiver::getReceiverGroupId, groups.get(0).getReceiverGroupId()); + } + //全部分账账号 List allMchReceiver = mchDivisionReceiverService.list(queryWrapper); if(allMchReceiver.isEmpty()){ @@ -228,7 +263,6 @@ public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceive return new ArrayList<>(); } - // 过滤账号 List filterMchReceiver = new ArrayList<>(); diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/division/DivisionReceiverBindRQ.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/division/DivisionReceiverBindRQ.java new file mode 100644 index 0000000..c79385c --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/division/DivisionReceiverBindRQ.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jeequan.jeepay.pay.rqrs.division; + +import com.jeequan.jeepay.pay.rqrs.AbstractMchAppRQ; +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/* +* 分账账号的绑定 请求参数 +* +* @author terrfly +* @site https://www.jeequan.com +* @date 2021/8/25 09:21 +*/ +@Data +public class DivisionReceiverBindRQ extends AbstractMchAppRQ { + + /** 支付接口代码 **/ + @NotBlank(message="支付接口代码不能为空") + private String ifCode; + + /** 接收者账号别名 **/ + private String receiverAlias; + + /** 组ID **/ + @NotNull(message="组ID不能为空, 若不存在请先登录商户平台进行创建操作") + private Long receiverGroupId; + + /** 分账接收账号类型: 0-个人(对私) 1-商户(对公) **/ + @NotNull(message="分账接收账号类型不能为空") + @Range(min = 0, max = 1, message = "分账接收账号类型设置有误") + private Byte accType; + + /** 分账接收账号 **/ + @NotBlank(message="分账接收账号不能为空") + private String accNo; + + /** 分账接收账号名称 **/ + private String accName; + + /** 分账关系类型(参考微信), 如: SERVICE_PROVIDER 服务商等 **/ + @NotBlank(message="分账关系类型不能为空") + private String relationType; + + /** 当选择自定义时,需要录入该字段。 否则为对应的名称 **/ + private String relationTypeName; + + /** 渠道特殊信息 */ + private String channelExtInfo; + + /** 分账比例 **/ + @NotBlank(message="分账比例不能为空") + private String divisionProfit; + +} diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/division/DivisionReceiverBindRS.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/division/DivisionReceiverBindRS.java new file mode 100644 index 0000000..4a57545 --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/division/DivisionReceiverBindRS.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jeequan.jeepay.pay.rqrs.division; + +import com.jeequan.jeepay.core.entity.MchDivisionReceiver; +import com.jeequan.jeepay.pay.rqrs.AbstractRS; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.math.BigDecimal; + +/* +* 绑定账户 响应参数 +* +* @author terrfly +* @site https://www.jeequan.com +* @date 2021/6/8 17:34 +*/ +@Data +public class DivisionReceiverBindRS extends AbstractRS { + + + /** + * 分账接收者ID + */ + private Long receiverId; + + /** + * 接收者账号别名 + */ + private String receiverAlias; + + /** + * 组ID(便于商户接口使用) + */ + private Long receiverGroupId; + + /** + * 商户号 + */ + private String mchNo; + + /** + * 应用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 String channelExtInfo; + + /** + * 绑定成功时间 + */ + private Long bindSuccessTime; + + /** + * 分账比例 + */ + private BigDecimal divisionProfit; + + /** + * 分账状态 1-绑定成功, 0-绑定异常 + */ + private Byte bindState; + + /** + * 支付渠道错误码 + */ + private String errCode; + + /** + * 支付渠道错误信息 + */ + private String errMsg; + + + + public static DivisionReceiverBindRS buildByRecord(MchDivisionReceiver record){ + + if(record == null){ + return null; + } + + DivisionReceiverBindRS result = new DivisionReceiverBindRS(); + BeanUtils.copyProperties(record, result); + result.setBindSuccessTime(record.getBindSuccessTime() != null ? record.getBindSuccessTime().getTime() : null); + + return result; + } + + + +} diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/ConfigContextService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/ConfigContextService.java index aa539da..1104178 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/ConfigContextService.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/ConfigContextService.java @@ -50,6 +50,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -181,6 +182,12 @@ public class ConfigContextService { return ; } + + // 商户应用mchNo 与参数不匹配 + if(!dbMchApp.getMchNo().equals(mchNo)){ + return; + } + //更新商户信息主体中的商户应用 mchInfoConfigContext.putMchApp(dbMchApp); @@ -256,19 +263,28 @@ public class ConfigContextService { /** 初始化 [ISV支付参数配置信息] **/ public synchronized void initIsvConfigContext(String isvNo){ + //查询出所有商户的配置信息并更新 + List mchNoList = new ArrayList<>(); + mchInfoService.list(MchInfo.gw().select(MchInfo::getMchNo).eq(MchInfo::getIsvNo, isvNo)).forEach(r -> mchNoList.add(r.getMchNo())); + + // 查询出所有 所属当前服务商的所有应用集合 + List mchAppIdList = new ArrayList<>(); + if(!mchNoList.isEmpty()){ + mchAppService.list(MchApp.gw().select(MchApp::getAppId).in(MchApp::getMchNo, mchNoList)).forEach(r -> mchAppIdList.add(r.getAppId())); + } + IsvConfigContext isvConfigContext = new IsvConfigContext(); IsvInfo isvInfo = isvInfoService.getById(isvNo); if(isvInfo == null){ - //查询出所有商户的配置信息并更新 - mchInfoService.list(MchInfo.gw().select(MchInfo::getMchNo).eq(MchInfo::getIsvNo, isvNo)).forEach(mchInfoItem -> { - + for (String appId : mchAppIdList) { //将更新已存在缓存的商户配置信息 (每个商户下存储的为同一个 服务商配置的对象指针) - MchAppConfigContext mchAppConfigContext = mchAppConfigContextMap.get(mchInfoItem.getMchNo()); + MchAppConfigContext mchAppConfigContext = mchAppConfigContextMap.get(appId); if(mchAppConfigContext != null){ mchAppConfigContext.setIsvConfigContext(null); } - }); + } + isvConfigContextMap.remove(isvNo); // 服务商有商户不可删除, 此处不再更新商户下的配置信息 return ; } @@ -314,16 +330,13 @@ public class ConfigContextService { isvConfigContextMap.put(isvNo, isvConfigContext); //查询出所有商户的配置信息并更新 - mchInfoService.list(MchInfo.gw().select(MchInfo::getMchNo).eq(MchInfo::getIsvNo, isvNo)).forEach(mchInfoItem -> { - + for (String appId : mchAppIdList) { //将更新已存在缓存的商户配置信息 (每个商户下存储的为同一个 服务商配置的对象指针) - MchAppConfigContext mchAppConfigContext = mchAppConfigContextMap.get(mchInfoItem.getMchNo()); + MchAppConfigContext mchAppConfigContext = mchAppConfigContextMap.get(appId); if(mchAppConfigContext != null){ mchAppConfigContext.setIsvConfigContext(isvConfigContext); } - - }); - + } } diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayOrderProcessService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayOrderProcessService.java index 37f6873..e6e98fa 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayOrderProcessService.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayOrderProcessService.java @@ -75,7 +75,7 @@ public class PayOrderProcessService { if(updDivisionState){ //推送到分账MQ - mqSender.send(PayOrderDivisionMQ.build(payOrder.getPayOrderId(), null), 1); //1分钟后执行 + mqSender.send(PayOrderDivisionMQ.build(payOrder.getPayOrderId(), null), 60); //1分钟后执行 } } catch (Exception e) { diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/util/CodeImgUtil.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/util/CodeImgUtil.java index 95d1343..98e857b 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/util/CodeImgUtil.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/util/CodeImgUtil.java @@ -69,7 +69,6 @@ public class CodeImgUtil { } - //TODO // 图片宽度的一般 private static final int IMAGE_WIDTH = 25; private static final int IMAGE_HEIGHT = 25; diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/MchDivisionReceiverGroupService.java b/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/MchDivisionReceiverGroupService.java new file mode 100644 index 0000000..dcf3afc --- /dev/null +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/MchDivisionReceiverGroupService.java @@ -0,0 +1,25 @@ +package com.jeequan.jeepay.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup; +import com.jeequan.jeepay.service.mapper.MchDivisionReceiverGroupMapper; +import org.springframework.stereotype.Service; + +/** + *

+ * 分账账号组 服务实现类 + *

+ * + * @author [mybatis plus generator] + * @since 2021-08-23 + */ +@Service +public class MchDivisionReceiverGroupService extends ServiceImpl { + + + /** 根据ID和商户号查询 **/ + public MchDivisionReceiverGroup findByIdAndMchNo(Long groupId, String mchNo){ + return getOne(MchDivisionReceiverGroup.gw().eq(MchDivisionReceiverGroup::getReceiverGroupId, groupId).eq(MchDivisionReceiverGroup::getMchNo, mchNo)); + } + +} diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderDivisionRecordService.java b/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderDivisionRecordService.java index 7c090c3..2afdf5f 100644 --- a/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderDivisionRecordService.java +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderDivisionRecordService.java @@ -5,6 +5,9 @@ import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord; import com.jeequan.jeepay.service.mapper.PayOrderDivisionRecordMapper; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; + /** *

* 分账记录表 服务实现类 @@ -16,4 +19,27 @@ import org.springframework.stereotype.Service; @Service public class PayOrderDivisionRecordService extends ServiceImpl { + + /** 更新分账记录为分账成功**/ + public void updateRecordSuccessOrFail(List records, Byte state, String channelBatchOrderId, String channelRespResult){ + + if(records == null || records.isEmpty()){ + return ; + } + + List recordIds = new ArrayList<>(); + records.stream().forEach(r -> recordIds.add(r.getRecordId())); + + PayOrderDivisionRecord updateRecord = new PayOrderDivisionRecord(); + updateRecord.setState(state); + updateRecord.setChannelBatchOrderId(channelBatchOrderId); + updateRecord.setChannelRespResult(channelRespResult); + update(updateRecord, PayOrderDivisionRecord.gw().in(PayOrderDivisionRecord::getRecordId, recordIds).eq(PayOrderDivisionRecord::getState, PayOrderDivisionRecord.STATE_WAIT)); + + } + + + + + } diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderService.java b/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderService.java index 00671f6..2e9f1fa 100644 --- a/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderService.java +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderService.java @@ -27,10 +27,8 @@ import com.jeequan.jeepay.core.entity.IsvInfo; import com.jeequan.jeepay.core.entity.MchInfo; import com.jeequan.jeepay.core.entity.PayOrder; import com.jeequan.jeepay.core.entity.PayWay; -import com.jeequan.jeepay.service.mapper.IsvInfoMapper; -import com.jeequan.jeepay.service.mapper.MchInfoMapper; -import com.jeequan.jeepay.service.mapper.PayOrderMapper; -import com.jeequan.jeepay.service.mapper.PayWayMapper; +import com.jeequan.jeepay.core.utils.AmountUtil; +import com.jeequan.jeepay.service.mapper.*; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -53,6 +51,7 @@ public class PayOrderService extends ServiceImpl { @Autowired private MchInfoMapper mchInfoMapper; @Autowired private IsvInfoMapper isvInfoMapper; @Autowired private PayWayMapper payWayMapper; + @Autowired private PayOrderDivisionRecordMapper payOrderDivisionRecordMapper; /** 更新订单状态 【订单生成】 --》 【支付中】 **/ public boolean updateInit2Ing(String payOrderId, PayOrder payOrder){ @@ -65,7 +64,6 @@ public class PayOrderService extends ServiceImpl { updateRecord.setWayCode(payOrder.getWayCode()); updateRecord.setMchFeeRate(payOrder.getMchFeeRate()); updateRecord.setMchFeeAmount(payOrder.getMchFeeAmount()); - updateRecord.setMchIncomeAmount(payOrder.getMchIncomeAmount()); return update(updateRecord, new LambdaUpdateWrapper() .eq(PayOrder::getPayOrderId, payOrderId).eq(PayOrder::getState, PayOrder.STATE_INIT)); @@ -358,4 +356,26 @@ public class PayOrderService extends ServiceImpl { payListMap.addAll(refundListMap); return payListMap; } + + + /** + * 计算支付订单商家入账金额 + * 商家订单入账金额 (支付金额 - 手续费 - 退款金额 - 总分账金额) + * @author terrfly + * @site https://www.jeepay.vip + * @date 2021/8/26 16:39 + */ + public Long calMchIncomeAmount(PayOrder dbPayOrder){ + + //商家订单入账金额 (支付金额 - 手续费 - 退款金额 - 总分账金额) + Long mchIncomeAmount = dbPayOrder.getAmount() - dbPayOrder.getMchFeeAmount() - dbPayOrder.getRefundAmount(); + + //减去已分账金额 + mchIncomeAmount -= payOrderDivisionRecordMapper.sumSuccessDivisionAmount(dbPayOrder.getPayOrderId()); + + return mchIncomeAmount <= 0 ? 0 : mchIncomeAmount; + + } + + } diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverGroupMapper.java b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverGroupMapper.java new file mode 100644 index 0000000..cbcb70f --- /dev/null +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverGroupMapper.java @@ -0,0 +1,16 @@ +package com.jeequan.jeepay.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup; + +/** + *

+ * 分账账号组 Mapper 接口 + *

+ * + * @author [mybatis plus generator] + * @since 2021-08-23 + */ +public interface MchDivisionReceiverGroupMapper extends BaseMapper { + +} diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverGroupMapper.xml b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverGroupMapper.xml new file mode 100644 index 0000000..dfffbcc --- /dev/null +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverGroupMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverMapper.xml b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverMapper.xml index 2306619..4cf1b68 100644 --- a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverMapper.xml +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/MchDivisionReceiverMapper.xml @@ -5,8 +5,9 @@ + - + @@ -18,7 +19,6 @@ - diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderDivisionRecordMapper.java b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderDivisionRecordMapper.java index c4604ce..5f9ed16 100644 --- a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderDivisionRecordMapper.java +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderDivisionRecordMapper.java @@ -13,4 +13,7 @@ import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord; */ public interface PayOrderDivisionRecordMapper extends BaseMapper { + /** 查询全部分账成功金额 **/ + Long sumSuccessDivisionAmount(String payOrderId); + } diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderDivisionRecordMapper.xml b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderDivisionRecordMapper.xml index 982676e..36b9acc 100644 --- a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderDivisionRecordMapper.xml +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderDivisionRecordMapper.xml @@ -21,6 +21,7 @@ + @@ -32,4 +33,11 @@ + + + + diff --git a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderMapper.xml b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderMapper.xml index dd830ae..3123c1e 100644 --- a/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderMapper.xml +++ b/jeepay-service/src/main/java/com/jeequan/jeepay/service/mapper/PayOrderMapper.xml @@ -16,7 +16,6 @@ - diff --git a/pom.xml b/pom.xml index 2518060..04e532e 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ UTF-8 - 1.2.0 + 1.3.0 1.2.76 3.4.2 5.6.6 @@ -150,7 +150,7 @@ com.alipay.sdk alipay-sdk-java - 4.13.50.ALL + 4.16.11.ALL