From 5c110d3a7157bdb85c37b8503d1843c98e89564d Mon Sep 17 00:00:00 2001 From: terrfly Date: Fri, 13 Aug 2021 22:08:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=95=86=E6=88=B7=E7=B3=BB=E7=BB=9F=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=BD=AC=E8=B4=A6=E5=8A=9F=E8=83=BD=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/sql/init.sql | 5 + docs/sql/patch.sql | 5 + .../ChannelUserIdNotifyController.java | 55 ++++++ .../ctrl/transfer/MchTransferController.java | 149 ++++++++++++++++ .../server/WsChannelUserIdServer.java | 167 ++++++++++++++++++ .../channelUser/getChannelUserIdPage.ftl | 67 +++++++ .../pay/ctrl/qr/ChannelUserIdController.java | 8 +- .../jeepay/pay/rqrs/ChannelUserIdRQ.java | 3 + pom.xml | 2 +- 9 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/transfer/ChannelUserIdNotifyController.java create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/transfer/MchTransferController.java create mode 100644 jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/server/WsChannelUserIdServer.java create mode 100644 jeepay-merchant/src/main/resources/templates/channelUser/getChannelUserIdPage.ftl diff --git a/docs/sql/init.sql b/docs/sql/init.sql index 20029f9..3833b60 100644 --- a/docs/sql/init.sql +++ b/docs/sql/init.sql @@ -548,6 +548,11 @@ insert into t_sys_entitlement values('ENT_MCH_CENTER', '商户中心', 'team', ' insert into t_sys_entitlement values('ENT_MCH_PAY_TEST_PAYWAY_LIST', '页面:获取全部支付方式', 'no-icon', '', '', 'PB', 0, 1, 'ENT_MCH_PAY_TEST', '0', 'MCH', now(), now()); insert into t_sys_entitlement values('ENT_MCH_PAY_TEST_DO', '按钮:支付测试', 'no-icon', '', '', 'PB', 0, 1, 'ENT_MCH_PAY_TEST', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_MCH_TRANSFER', '转账', 'property-safety', '/doTransfer', 'MchTransferPage', 'ML', 0, 1, 'ENT_MCH_CENTER', '30', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_MCH_TRANSFER_IF_CODE_LIST', '页面:获取全部代付通道', 'no-icon', '', '', 'PB', 0, 1, 'ENT_MCH_TRANSFER', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_MCH_TRANSFER_CHANNEL_USER', '按钮:获取渠道用户', 'no-icon', '', '', 'PB', 0, 1, 'ENT_MCH_TRANSFER', '0', 'MCH', now(), now()); + insert into t_sys_entitlement values('ENT_MCH_TRANSFER_DO', '按钮:发起转账', 'no-icon', '', '', 'PB', 0, 1, 'ENT_MCH_TRANSFER', '0', 'MCH', now(), now()); + -- 【商户系统】 订单管理 insert into t_sys_entitlement values('ENT_ORDER', '订单中心', 'transaction', '', 'RouteView', 'ML', 0, 1, 'ROOT', '20', 'MCH', now(), now()); insert into t_sys_entitlement values('ENT_PAY_ORDER', '订单管理', 'account-book', '/pay', 'PayOrderListPage', 'ML', 0, 1, 'ENT_ORDER', '10', 'MCH', now(), now()); diff --git a/docs/sql/patch.sql b/docs/sql/patch.sql index 65a920d..2507486 100644 --- a/docs/sql/patch.sql +++ b/docs/sql/patch.sql @@ -88,4 +88,9 @@ insert into t_sys_entitlement values('ENT_TRANSFER_ORDER_VIEW', '按钮:详情 insert into t_sys_entitlement values('ENT_TRANSFER_ORDER', '转账订单', 'property-safety', '/transfer', 'TransferOrderListPage', 'ML', 0, 1, 'ENT_ORDER', '30', 'MCH', now(), now()); 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_MCH_TRANSFER', '转账', 'property-safety', '/doTransfer', 'MchTransferPage', 'ML', 0, 1, 'ENT_MCH_CENTER', '30', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_MCH_TRANSFER_IF_CODE_LIST', '页面:获取全部代付通道', 'no-icon', '', '', 'PB', 0, 1, 'ENT_MCH_TRANSFER', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_MCH_TRANSFER_CHANNEL_USER', '按钮:获取渠道用户', 'no-icon', '', '', 'PB', 0, 1, 'ENT_MCH_TRANSFER', '0', 'MCH', now(), now()); +insert into t_sys_entitlement values('ENT_MCH_TRANSFER_DO', '按钮:发起转账', 'no-icon', '', '', 'PB', 0, 1, 'ENT_MCH_TRANSFER', '0', 'MCH', now(), now()); + ## -- ++++ ++++ diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/transfer/ChannelUserIdNotifyController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/transfer/ChannelUserIdNotifyController.java new file mode 100644 index 0000000..1ca39fb --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/transfer/ChannelUserIdNotifyController.java @@ -0,0 +1,55 @@ +/* + * 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.transfer; + +import com.alibaba.fastjson.JSONObject; +import com.jeequan.jeepay.mch.ctrl.CommonCtrl; +import com.jeequan.jeepay.mch.websocket.server.WsChannelUserIdServer; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +/** +* 获取用户ID - 回调函数 +* +* @author terrfly +* @site https://www.jeequan.com +* @date 2021/8/13 17:54 +*/ +@Controller +@RequestMapping("/api/anon/channelUserIdCallback") +public class ChannelUserIdNotifyController extends CommonCtrl { + + @RequestMapping("") + public String channelUserIdCallback() { + + try { + //请求参数 + JSONObject params = getReqParamJSON(); + + String extParam = params.getString("extParam"); + String channelUserId = params.getString("channelUserId"); + String appId = params.getString("appId"); + + //推送到前端 + WsChannelUserIdServer.sendMsgByAppAndCid(appId, extParam, channelUserId); + + } catch (Exception e) { + request.setAttribute("errMsg", e.getMessage()); + } + + return "channelUser/getChannelUserIdPage"; + } +} diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/transfer/MchTransferController.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/transfer/MchTransferController.java new file mode 100644 index 0000000..72f6d30 --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/transfer/MchTransferController.java @@ -0,0 +1,149 @@ +/* + * 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.transfer; + +import com.alibaba.fastjson.JSONObject; +import com.jeequan.jeepay.JeepayClient; +import com.jeequan.jeepay.core.constants.CS; +import com.jeequan.jeepay.core.entity.MchApp; +import com.jeequan.jeepay.core.entity.MchPayPassage; +import com.jeequan.jeepay.core.entity.PayInterfaceConfig; +import com.jeequan.jeepay.core.entity.PayInterfaceDefine; +import com.jeequan.jeepay.core.exception.BizException; +import com.jeequan.jeepay.core.model.ApiRes; +import com.jeequan.jeepay.core.utils.JeepayKit; +import com.jeequan.jeepay.core.utils.StringKit; +import com.jeequan.jeepay.exception.JeepayException; +import com.jeequan.jeepay.mch.ctrl.CommonCtrl; +import com.jeequan.jeepay.model.PayOrderCreateReqModel; +import com.jeequan.jeepay.model.TransferOrderCreateReqModel; +import com.jeequan.jeepay.model.TransferOrderCreateResModel; +import com.jeequan.jeepay.request.PayOrderCreateRequest; +import com.jeequan.jeepay.request.TransferOrderCreateRequest; +import com.jeequan.jeepay.response.PayOrderCreateResponse; +import com.jeequan.jeepay.response.TransferOrderCreateResponse; +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.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** +* 转账api +* +* @author terrfly +* @site https://www.jeequan.com +* @date 2021/8/13 14:43 +*/ +@RestController +@RequestMapping("/api/mchTransfers") +public class MchTransferController extends CommonCtrl { + + @Autowired private MchAppService mchAppService; + @Autowired private PayInterfaceConfigService payInterfaceConfigService; + @Autowired private PayInterfaceDefineService payInterfaceDefineService; + @Autowired private SysConfigService sysConfigService; + + /** 查询商户对应应用下支持的支付通道 **/ + @PreAuthorize("hasAuthority('ENT_MCH_TRANSFER_IF_CODE_LIST')") + @GetMapping("/ifCodes/{appId}") + public ApiRes ifCodeList(@PathVariable("appId") String appId) { + + + List ifCodeList = new ArrayList<>(); + payInterfaceConfigService.list( + PayInterfaceConfig.gw().select(PayInterfaceConfig::getIfCode) + .eq(PayInterfaceConfig::getInfoType, CS.INFO_TYPE_MCH_APP) + .eq(PayInterfaceConfig::getInfoId, appId) + .eq(PayInterfaceConfig::getState, CS.PUB_USABLE) + ).stream().forEach(r -> ifCodeList.add(r.getIfCode())); + + if(ifCodeList.isEmpty()){ + return ApiRes.ok(ifCodeList); + } + + List result = payInterfaceDefineService.list(PayInterfaceDefine.gw().in(PayInterfaceDefine::getIfCode, ifCodeList)); + return ApiRes.ok(result); + } + + + + /** 获取渠道侧用户ID **/ + @PreAuthorize("hasAuthority('ENT_MCH_TRANSFER_CHANNEL_USER')") + @GetMapping("/channelUserId") + public ApiRes channelUserId() { + + String appId = getValStringRequired("appId"); + MchApp mchApp = mchAppService.getById(appId); + if(mchApp == null || mchApp.getState() != CS.PUB_USABLE || !mchApp.getMchNo().equals(getCurrentMchNo())){ + throw new BizException("商户应用不存在或不可用"); + } + + JSONObject param = getReqParamJSON(); + param.put("mchNo", getCurrentMchNo()); + param.put("appId", appId); + param.put("ifCode", getValStringRequired("ifCode")); + param.put("extParam", getValStringRequired("extParam")); + param.put("reqTime", System.currentTimeMillis() + ""); + param.put("version", "1.0"); + param.put("signType", "MD5"); + param.put("redirectUrl", sysConfigService.getDBApplicationConfig().getMchSiteUrl() + "/api/anon/channelUserIdCallback"); + + param.put("sign", JeepayKit.getSign(param, mchApp.getAppSecret())); + String url = StringKit.appendUrlQuery(sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/api/channelUserId/jump", param); + + return ApiRes.ok(url); + } + + + /** 调起下单接口 **/ + @PreAuthorize("hasAuthority('ENT_MCH_PAY_TEST_DO')") + @PostMapping("/doTransfer") + public ApiRes doTransfer() { + + handleParamAmount("amount"); + TransferOrderCreateReqModel model = getObject(TransferOrderCreateReqModel.class); + + MchApp mchApp = mchAppService.getById(model.getAppId()); + if(mchApp == null || mchApp.getState() != CS.PUB_USABLE || !mchApp.getMchNo().equals(getCurrentMchNo()) ){ + throw new BizException("商户应用不存在或不可用"); + } + + TransferOrderCreateRequest request = new TransferOrderCreateRequest(); + model.setMchNo(this.getCurrentMchNo()); + model.setAppId(mchApp.getAppId()); + model.setCurrency("CNY"); + request.setBizModel(model); + + JeepayClient jeepayClient = new JeepayClient(sysConfigService.getDBApplicationConfig().getPaySiteUrl(), mchApp.getAppSecret()); + + try { + TransferOrderCreateResponse 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()); + } + } + +} diff --git a/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/server/WsChannelUserIdServer.java b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/server/WsChannelUserIdServer.java new file mode 100644 index 0000000..7678dcc --- /dev/null +++ b/jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/websocket/server/WsChannelUserIdServer.java @@ -0,0 +1,167 @@ +/* + * 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.websocket.server; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * WebSocket服务类 + * /ws/channelUserId/{appId}/{客戶端自定義ID} + * + * @author terrfly + * @site https://www.jeequan.com + * @date 2021/8/13 18:57 + */ +@ServerEndpoint("/api/anon/ws/channelUserId/{appId}/{cid}") +@Component +public class WsChannelUserIdServer { + + private final static Logger logger = LoggerFactory.getLogger(WsChannelUserIdServer.class); + + //当前在线客户端 数量 + private static int onlineClientSize = 0; + + // appId 与 WsPayOrderServer 存储关系, ConcurrentHashMap保证线程安全 + private static Map> wsAppIdMap = new ConcurrentHashMap<>(); + + //与某个客户端的连接会话,需要通过它来给客户端发送数据 + private Session session; + + //客户端自定义ID + private String cid = ""; + + //支付订单号 + private String appId = ""; + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("appId") String appId, @PathParam("cid") String cid) { + + try { + //设置当前属性 + this.cid = cid; + this.appId = appId; + this.session = session; + + Set wsServerSet = wsAppIdMap.get(appId); + if(wsServerSet == null) { + wsServerSet = new CopyOnWriteArraySet<>(); + } + wsServerSet.add(this); + wsAppIdMap.put(appId, wsServerSet); + + addOnlineCount(); //在线数加1 + logger.info("cid[{}],appId[{}]连接开启监听!当前在线人数为{}", cid, appId, onlineClientSize); + + } catch (Exception e) { + logger.error("ws监听异常cid[{}],appId[{}]", cid, appId, e); + } + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + + Set wsSet = wsAppIdMap.get(this.appId); + wsSet.remove(this); + if(wsSet.isEmpty()) { + wsAppIdMap.remove(this.appId); + } + + subOnlineCount(); //在线数减1 + logger.info("cid[{}],appId[{}]连接关闭!当前在线人数为{}", cid, appId, onlineClientSize); + } + + /** + * @param session + * @param error + */ + @OnError + public void onError(Session session, Throwable error) { + logger.error("ws发生错误", error); + } + + /** + * 实现服务器主动推送 + */ + public void sendMessage(String message) throws IOException { + this.session.getBasicRemote().sendText(message); + } + + /** + * 根据订单ID,推送消息 + * 捕捉所有的异常,避免影响业务。 + * @param appId + */ + public static void sendMsgByAppAndCid(String appId, String cid, String msg) { + + try { + logger.info("推送ws消息到浏览器, appId={}, cid={}, msg={}", appId, cid, msg); + + + Set wsSet = wsAppIdMap.get(appId); + if(wsSet == null || wsSet.isEmpty()){ + logger.info("appId[{}] 无ws监听客户端", appId); + return ; + } + + for (WsChannelUserIdServer item : wsSet) { + if(!cid.equals(item.cid)){ + continue; + } + try { + item.sendMessage(msg); + } catch (Exception e) { + logger.info("推送设备消息时异常,appId={}, cid={}", appId, item.cid, e); + continue; + } + } + } catch (Exception e) { + logger.info("推送消息时异常,appId={}", appId, e); + } + } + + public static synchronized int getOnlineClientSize() { + return onlineClientSize; + } + + public static synchronized void addOnlineCount() { + onlineClientSize++; + } + + public static synchronized void subOnlineCount() { + onlineClientSize--; + } + +} diff --git a/jeepay-merchant/src/main/resources/templates/channelUser/getChannelUserIdPage.ftl b/jeepay-merchant/src/main/resources/templates/channelUser/getChannelUserIdPage.ftl new file mode 100644 index 0000000..15de9cc --- /dev/null +++ b/jeepay-merchant/src/main/resources/templates/channelUser/getChannelUserIdPage.ftl @@ -0,0 +1,67 @@ + + + + + + 提示 + + + + + + +<#if errMsg != null > + +

+ +
+
+ 获取失败 +
+ 错误提示:${errMsg!''} + +
+
+ +<#else> + +
+ +
+
+ 获取成功 +
+ + + +
+ 关闭页面 +
+ + + + + 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 fa5a3f0..03c02ec 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 @@ -72,6 +72,7 @@ public class ChannelUserIdController extends AbstractPayOrderController { JSONObject jsonObject = new JSONObject(); jsonObject.put("mchNo", rq.getMchNo()); jsonObject.put("appId", rq.getAppId()); + jsonObject.put("extParam", rq.getExtParam()); jsonObject.put("ifCode", ifCode); jsonObject.put("redirectUrl", rq.getRedirectUrl()); @@ -95,6 +96,7 @@ public class ChannelUserIdController extends AbstractPayOrderController { String mchNo = callbackData.getString("mchNo"); String appId = callbackData.getString("appId"); String ifCode = callbackData.getString("ifCode"); + String extParam = callbackData.getString("extParam"); String redirectUrl = callbackData.getString("redirectUrl"); // 获取接口 @@ -111,7 +113,11 @@ public class ChannelUserIdController extends AbstractPayOrderController { String channelUserId = channelUserService.getChannelUserId(getReqParamJSON(), mchAppConfigContext); //同步跳转 - response.sendRedirect(StringKit.appendUrlQuery(redirectUrl, JsonKit.newJson("channelUserId", channelUserId))); + JSONObject appendParams = new JSONObject(); + appendParams.put("appId", appId); + appendParams.put("channelUserId", channelUserId); + appendParams.put("extParam", extParam); + response.sendRedirect(StringKit.appendUrlQuery(redirectUrl, appendParams)); } diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/ChannelUserIdRQ.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/ChannelUserIdRQ.java index fde7de5..4117f8a 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/ChannelUserIdRQ.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/rqrs/ChannelUserIdRQ.java @@ -33,6 +33,9 @@ public class ChannelUserIdRQ extends AbstractMchAppRQ{ @NotBlank(message="接口代码不能为空") private String ifCode; + /** 商户扩展参数,将原样返回 **/ + private String extParam; + /** 回调地址 **/ @NotBlank(message="回调地址不能为空") private String redirectUrl; diff --git a/pom.xml b/pom.xml index 292e782..f54c0db 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ UTF-8 - 1.1.0 + 1.2.0 1.2.76 3.4.2 5.6.6