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 627565a..f8b5843 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 @@ -64,4 +64,9 @@ public class DBApplicationConfig implements Serializable { return getPaySiteUrl() + "/api/scan/imgs/" + JeepayKit.aesEncode(url) + ".png"; } + /** 生成 【支付宝 isv子商户的授权链接地址】 **/ + public String genAlipayIsvsubMchAuthUrl(String isvNo, String mchAppId){ + return getPaySiteUrl() + "/api/channelbiz/alipay/redirectAppToAppAuth/" + isvNo + "_" + mchAppId; + } + } diff --git a/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/params/alipay/AlipayConfig.java b/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/params/alipay/AlipayConfig.java index 5038bac..0ec57ed 100644 --- a/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/params/alipay/AlipayConfig.java +++ b/jeepay-core/src/main/java/com/jeequan/jeepay/core/model/params/alipay/AlipayConfig.java @@ -38,6 +38,11 @@ public class AlipayConfig{ public static String PROD_OAUTH_URL = "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=%s&scope=auth_base&state=&redirect_uri=%s"; public static String SANDBOX_OAUTH_URL = "https://openauth.alipaydev.com/oauth2/publicAppAuthorize.htm?app_id=%s&scope=auth_base&state=&redirect_uri=%s"; + /** isv获取授权商户URL地址 **/ + public static String PROD_APP_TO_APP_AUTH_URL = "https://openauth.alipay.com/oauth2/appToAppAuth.htm?app_id=%s&redirect_uri=%s&state=%s"; + public static String SANDBOX_APP_TO_APP_AUTH_URL = "https://openauth.alipaydev.com/oauth2/appToAppAuth.htm?app_id=%s&redirect_uri=%s&state=%s"; + + public static String FORMAT = "json"; public static String CHARSET = "UTF-8"; diff --git a/jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/merchant/MchPayInterfaceConfigController.java b/jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/merchant/MchPayInterfaceConfigController.java index 4c5df74..abf5f66 100644 --- a/jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/merchant/MchPayInterfaceConfigController.java +++ b/jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/merchant/MchPayInterfaceConfigController.java @@ -32,7 +32,6 @@ import com.jeequan.jeepay.mgr.ctrl.CommonCtrl; import com.jeequan.jeepay.service.impl.MchAppService; import com.jeequan.jeepay.service.impl.MchInfoService; import com.jeequan.jeepay.service.impl.PayInterfaceConfigService; -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.*; @@ -52,8 +51,8 @@ import java.util.List; public class MchPayInterfaceConfigController extends CommonCtrl { @Autowired private PayInterfaceConfigService payInterfaceConfigService; - @Autowired private MqCommonService mqCommonService; @Autowired private MchAppService mchAppService; + @Autowired private MqCommonService mqCommonService; @Autowired private MchInfoService mchInfoService; /** @@ -158,4 +157,21 @@ public class MchPayInterfaceConfigController extends CommonCtrl { return ApiRes.ok(); } + + + /** 查询支付宝商户授权URL **/ + @GetMapping("/alipayIsvsubMchAuthUrls/{mchAppId}") + public ApiRes queryAlipayIsvsubMchAuthUrl(@PathVariable String mchAppId) { + + MchApp mchApp = mchAppService.getById(mchAppId); + MchInfo mchInfo = mchInfoService.getById(mchApp.getMchNo()); + String authUrl = sysConfigService.getDBApplicationConfig().genAlipayIsvsubMchAuthUrl(mchInfo.getIsvNo(), mchAppId); + String authQrImgUrl = sysConfigService.getDBApplicationConfig().genScanImgUrl(authUrl); + + JSONObject result = new JSONObject(); + result.put("authUrl", authUrl); + result.put("authQrImgUrl", authQrImgUrl); + return ApiRes.ok(result); + } + } 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 new file mode 100644 index 0000000..c1d88f1 --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/ctrl/AlipayBizController.java @@ -0,0 +1,161 @@ +/* + * 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.ctrl; + +import cn.hutool.core.util.URLUtil; +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.domain.AlipayOpenAuthTokenAppModel; +import com.alipay.api.request.AlipayOpenAuthTokenAppRequest; +import com.alipay.api.response.AlipayOpenAuthTokenAppResponse; +import com.jeequan.jeepay.core.constants.CS; +import com.jeequan.jeepay.core.ctrls.AbstractCtrl; +import com.jeequan.jeepay.core.entity.MchApp; +import com.jeequan.jeepay.core.entity.PayInterfaceConfig; +import com.jeequan.jeepay.core.exception.BizException; +import com.jeequan.jeepay.core.model.params.alipay.AlipayConfig; +import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams; +import com.jeequan.jeepay.core.mq.MqCommonService; +import com.jeequan.jeepay.core.utils.JsonKit; +import com.jeequan.jeepay.pay.channel.alipay.AlipayKit; +import com.jeequan.jeepay.pay.model.AlipayClientWrapper; +import com.jeequan.jeepay.pay.model.IsvConfigContext; +import com.jeequan.jeepay.pay.service.ConfigContextService; +import com.jeequan.jeepay.service.impl.MchAppService; +import com.jeequan.jeepay.service.impl.PayInterfaceConfigService; +import com.jeequan.jeepay.service.impl.SysConfigService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.io.IOException; +import java.math.BigDecimal; + +/** +* 渠道侧自定义业务ctrl +* +* @author terrfly +* @site https://www.jeepay.vip +* @date 2021/7/15 11:49 +*/ +@Slf4j +@Controller +@RequestMapping("/api/channelbiz/alipay") +public class AlipayBizController extends AbstractCtrl { + + @Autowired private ConfigContextService configContextService; + @Autowired private SysConfigService sysConfigService; + @Autowired private PayInterfaceConfigService payInterfaceConfigService; + @Autowired private MqCommonService mqCommonService; + @Autowired private MchAppService mchAppService; + + /** 跳转到支付宝的授权页面 (统一从pay项目获取到isv配置信息) + * isvAndMchNo 格式: ISVNO_MCHAPPID + * example: https://pay.jeepay.cn/api/channelbiz/alipay/redirectAppToAppAuth/V1623998765_60cc41694ee0e6685f57eb1f + * **/ + @RequestMapping("/redirectAppToAppAuth/{isvAndMchAppId}") + public void redirectAppToAppAuth(@PathVariable("isvAndMchAppId") String isvAndMchAppId) throws IOException { + + String isvNo = isvAndMchAppId.split("_")[0]; + + IsvConfigContext isvConfigContext = configContextService.getIsvConfigContext(isvNo); + AlipayIsvParams alipayIsvParams = isvConfigContext.getIsvParamsByIfCode(CS.IF_CODE.ALIPAY, AlipayIsvParams.class); + alipayIsvParams.getSandbox(); + + String oauthUrl = AlipayConfig.PROD_APP_TO_APP_AUTH_URL; + if(alipayIsvParams.getSandbox() != null && alipayIsvParams.getSandbox() == CS.YES){ + oauthUrl = AlipayConfig.SANDBOX_APP_TO_APP_AUTH_URL; + } + + String redirectUrl = sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/api/channelbiz/alipay/appToAppAuthCallback"; + response.sendRedirect(String.format(oauthUrl, alipayIsvParams.getAppId(), URLUtil.encode(redirectUrl), isvAndMchAppId)); + } + + /** 支付宝授权回调地址 **/ + @RequestMapping("/appToAppAuthCallback") + public String appToAppAuthCallback() { + + String errMsg = null; + boolean isAlipaySysAuth = true; //是否 服务商登录支付宝后台系统发起的商户授权, 此时无法获取authCode和商户的信息。 + + try { + // isvAndMchAppId 格式: ISVNO_MCHAPPID, 如果isvAndMchNo为空说明是: 支付宝后台的二维码授权之后的跳转链接。 + String isvAndMchAppId = getValString("state"); + String appAuthCode = getValString("app_auth_code"); // 支付宝授权code + + if(StringUtils.isNotEmpty(isvAndMchAppId) && StringUtils.isNotEmpty(appAuthCode)){ + isAlipaySysAuth = false; + String isvNo = isvAndMchAppId.split("_")[0]; + String mchAppId = isvAndMchAppId.split("_")[1]; + AlipayClientWrapper alipayClientWrapper = configContextService.getIsvConfigContext(isvNo).getAlipayClientWrapper(); + + AlipayOpenAuthTokenAppRequest request = new AlipayOpenAuthTokenAppRequest(); + AlipayOpenAuthTokenAppModel model = new AlipayOpenAuthTokenAppModel(); + model.setGrantType("authorization_code"); + model.setCode(appAuthCode); + request.setBizModel(model); + + // expiresIn: 该字段已作废,应用令牌长期有效,接入方不需要消费该字段 + // reExpiresIn: 刷新令牌的有效时间(从接口调用时间作为起始时间),单位到秒 + // DateUtil.offsetSecond(new Date(), Integer.parseInt(resp.getExpiresIn())); + AlipayOpenAuthTokenAppResponse resp = alipayClientWrapper.execute(request); + if(!resp.isSuccess()){ + throw new BizException(AlipayKit.appendErrMsg(resp.getMsg(), resp.getSubMsg())); + } + String appAuthToken = resp.getAppAuthToken(); + JSONObject ifParams = new JSONObject(); + ifParams.put("appAuthToken", appAuthToken); ifParams.put("refreshToken", resp.getAppRefreshToken()); ifParams.put("expireTimestamp", resp.getExpiresIn()); + + PayInterfaceConfig dbRecord = payInterfaceConfigService.getByInfoIdAndIfCode(CS.INFO_TYPE_MCH_APP, mchAppId, CS.IF_CODE.ALIPAY); + + if(dbRecord != null){ + PayInterfaceConfig updateRecord = new PayInterfaceConfig(); + updateRecord.setId(dbRecord.getId()); updateRecord.setIfParams(ifParams.toJSONString()); + payInterfaceConfigService.updateById(updateRecord); + }else{ + + dbRecord = new PayInterfaceConfig(); + dbRecord.setInfoType(CS.INFO_TYPE_MCH_APP); + dbRecord.setInfoId(mchAppId); + dbRecord.setIfCode(CS.IF_CODE.ALIPAY); + dbRecord.setIfParams(ifParams.toJSONString()); + dbRecord.setIfRate(new BigDecimal("0.006")); //默认费率 + dbRecord.setState(CS.YES); + dbRecord.setCreatedBy("SYS"); + dbRecord.setCreatedUid(0L); + payInterfaceConfigService.save(dbRecord); + } + + MchApp mchApp = mchAppService.getById(mchAppId); + + JSONObject jsonObject = JsonKit.newJson("mchNo", mchApp.getMchNo()); + jsonObject.put("appId", mchApp.getAppId()); + mqCommonService.send(jsonObject.toJSONString(), CS.MQ.MQ_TYPE_MODIFY_MCH_APP); // 推送mq到目前节点进行更新数据 + + } + } catch (Exception e) { + log.error("error", e); + errMsg = StringUtils.defaultIfBlank(e.getMessage(), "系统异常!"); + } + + request.setAttribute("errMsg", errMsg); + request.setAttribute("isAlipaySysAuth", isAlipaySysAuth); + return "channel/alipay/isvsubMchAuth"; + } + +} diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/ActiveMqMessage.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/ActiveMqMessage.java index af9dab5..9b27def 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/ActiveMqMessage.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/ActiveMqMessage.java @@ -22,6 +22,7 @@ import com.jeequan.jeepay.pay.mq.receive.MqReceiveCommon; import lombok.extern.slf4j.Slf4j; import org.apache.activemq.ScheduledMessage; import org.apache.activemq.command.ActiveMQQueue; +import org.apache.activemq.command.ActiveMQTopic; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; @@ -75,6 +76,16 @@ public class ActiveMqMessage extends MqCommonService { @Qualifier("activePayOrderMchNotifyInner") private Queue mqQueue4PayOrderMchNotifyInner; + @Bean("activeMqSendModifyMchApp") + public ActiveMQTopic mqTopic4ModifyMchApp(){ + return new ActiveMQTopic(CS.MQ.TOPIC_MODIFY_MCH_APP); + } + + @Lazy + @Autowired + @Qualifier("activeMqSendModifyMchApp") + private ActiveMQTopic mqTopic4ModifyMchApp; + /** * 发送消息 * @param msg @@ -86,6 +97,8 @@ public class ActiveMqMessage extends MqCommonService { channelOrderQuery(msg); }else if (sendType.equals(CS.MQ.MQ_TYPE_PAY_ORDER_MCH_NOTIFY)) { payOrderMchNotify(msg); + }else if (sendType.equals(CS.MQ.MQ_TYPE_MODIFY_MCH_APP)) { // 商户应用修改 + this.jmsTemplate.convertAndSend(mqTopic4ModifyMchApp, msg); } } diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/RabbitMqMessage.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/RabbitMqMessage.java index f6c0560..e880530 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/RabbitMqMessage.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/RabbitMqMessage.java @@ -53,6 +53,8 @@ public class RabbitMqMessage extends MqCommonService { channelOrderQuery(msg); }else if (sendType.equals(CS.MQ.MQ_TYPE_PAY_ORDER_MCH_NOTIFY)) { payOrderMchNotify(msg); + }else if (sendType.equals(CS.MQ.MQ_TYPE_MODIFY_MCH_APP)) { // 商户应用修改 + directModifyMchApp(msg); } } @@ -91,6 +93,12 @@ public class RabbitMqMessage extends MqCommonService { }); } + /** 发送商户应用修改消息 **/ + public void directModifyMchApp(String msg) { + rabbitTemplate.convertAndSend(CS.DIRECT_EXCHANGE, CS.MQ.TOPIC_MODIFY_MCH_APP, msg); + } + + /** 接收 查单消息 **/ @RabbitListener(queues = CS.MQ.QUEUE_CHANNEL_ORDER_QUERY) public void receiveChannelOrderQuery(String msg) { diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/RocketMqMessage.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/RocketMqMessage.java index d63a1e0..0f1af61 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/RocketMqMessage.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/RocketMqMessage.java @@ -57,6 +57,8 @@ public class RocketMqMessage extends MqCommonService { channelOrderQuery(msg); }else if (sendType.equals(CS.MQ.MQ_TYPE_PAY_ORDER_MCH_NOTIFY)) { payOrderMchNotify(msg); + }else if (sendType.equals(CS.MQ.MQ_TYPE_MODIFY_MCH_INFO)) { // 商户信息修改 + modifyMchInfo(msg); } } @@ -127,4 +129,17 @@ public class RocketMqMessage extends MqCommonService { mqReceiveCommon.payOrderMchNotify(msg); } } + + + /** 发送商户信息修改消息 **/ + public void modifyMchInfo(String msg) { + sendMsg(msg, CS.MQ.TOPIC_MODIFY_MCH_INFO); + } + + public void sendMsg(String msg, String group) { + // 这里的分组和消息名称未做区分 + rocketMQTemplate.getProducer().setProducerGroup(group); + this.rocketMQTemplate.convertAndSend(group, msg); + } + } diff --git a/jeepay-payment/src/main/resources/templates/channel/alipay/isvsubMchAuth.ftl b/jeepay-payment/src/main/resources/templates/channel/alipay/isvsubMchAuth.ftl new file mode 100644 index 0000000..c190b95 --- /dev/null +++ b/jeepay-payment/src/main/resources/templates/channel/alipay/isvsubMchAuth.ftl @@ -0,0 +1,73 @@ + + +
+ + +