diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/AbstractTransferNoticeService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/AbstractTransferNoticeService.java new file mode 100644 index 0000000..993a9ff --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/AbstractTransferNoticeService.java @@ -0,0 +1,86 @@ +/* + * 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; + +import com.alibaba.fastjson.JSONObject; +import com.jeequan.jeepay.core.beans.RequestKitBean; +import com.jeequan.jeepay.pay.service.ConfigContextQueryService; +import com.jeequan.jeepay.pay.util.ChannelCertConfigKitBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; + +/* +* 实现回调接口抽象类 +* +* @author zx +* @site https://www.jeequan.com +* @date 2022/12/30 10:18 +*/ +public abstract class AbstractTransferNoticeService implements ITransferNoticeService { + + @Autowired private RequestKitBean requestKitBean; + @Autowired private ChannelCertConfigKitBean channelCertConfigKitBean; + @Autowired protected ConfigContextQueryService configContextQueryService; + + @Override + public ResponseEntity doNotifyOrderNotExists(HttpServletRequest request) { + return textResp("order not exists"); + } + + /** 文本类型的响应数据 **/ + protected ResponseEntity textResp(String text){ + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.TEXT_HTML); + return new ResponseEntity(text, httpHeaders, HttpStatus.OK); + } + + /** json类型的响应数据 **/ + protected ResponseEntity jsonResp(Object body){ + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + return new ResponseEntity(body, httpHeaders, HttpStatus.OK); + } + + + /**request.getParameter 获取参数 并转换为JSON格式 **/ + protected JSONObject getReqParamJSON() { + return requestKitBean.getReqParamJSON(); + } + + /**request.getParameter 获取参数 并转换为JSON格式 **/ + protected String getReqParamFromBody() { + return requestKitBean.getReqParamFromBody(); + } + + /** 获取文件路径 **/ + protected String getCertFilePath(String certFilePath) { + return channelCertConfigKitBean.getCertFilePath(certFilePath); + } + + /** 获取文件File对象 **/ + protected File getCertFile(String certFilePath) { + return channelCertConfigKitBean.getCertFile(certFilePath); + } + +} diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/ITransferNoticeService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/ITransferNoticeService.java new file mode 100644 index 0000000..853ead1 --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/ITransferNoticeService.java @@ -0,0 +1,50 @@ +/* + * 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; + +import com.jeequan.jeepay.core.entity.TransferOrder; +import com.jeequan.jeepay.pay.model.MchAppConfigContext; +import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg; +import org.apache.commons.lang3.tuple.MutablePair; +import org.springframework.http.ResponseEntity; + +import javax.servlet.http.HttpServletRequest; + +/* +* 转账订单通知解析实现 异步回调 +* +* @author zx +* @site https://www.jeequan.com +* @date 2022/12/30 10:14 +*/ +public interface ITransferNoticeService { + + /** 获取到接口code **/ + String getIfCode(); + + /** 解析参数: 转账单号 和 请求参数 + * 异常需要自行捕捉,并返回null , 表示已响应数据。 + * **/ + MutablePair parseParams(HttpServletRequest request, String urlOrderId); + + /** 返回需要更新的订单状态 和响应数据 **/ + ChannelRetMsg doNotice(HttpServletRequest request, + Object params, TransferOrder transferOrder, MchAppConfigContext mchAppConfigContext); + + /** 数据库订单数据不存在 (仅异步通知使用) **/ + ResponseEntity doNotifyOrderNotExists(HttpServletRequest request); + +} diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayTransferNoticeService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayTransferNoticeService.java new file mode 100644 index 0000000..a7989f3 --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayTransferNoticeService.java @@ -0,0 +1,143 @@ +/* + * 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.JSONObject; +import com.alipay.api.internal.util.AlipaySignature; +import com.jeequan.jeepay.core.constants.CS; +import com.jeequan.jeepay.core.entity.TransferOrder; +import com.jeequan.jeepay.core.exception.ResponseException; +import com.jeequan.jeepay.core.model.params.alipay.AlipayConfig; +import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams; +import com.jeequan.jeepay.core.model.params.alipay.AlipayNormalMchParams; +import com.jeequan.jeepay.pay.channel.AbstractTransferNoticeService; +import com.jeequan.jeepay.pay.model.MchAppConfigContext; +import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.MutablePair; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/* +* 支付宝 转账回调接口实现类 +* +* @author zx +* @site https://www.jeequan.com +* @date 2021/21/01 17:16 +*/ +@Service +@Slf4j +public class AlipayTransferNoticeService extends AbstractTransferNoticeService { + + @Override + public String getIfCode() { + return CS.IF_CODE.ALIPAY; + } + + @Override + public MutablePair parseParams(HttpServletRequest request, String urlOrderId) { + + try { + + JSONObject params = getReqParamJSON(); + log.info("【支付宝转账】回调通知参数:{}", params.toJSONString()); + + JSONObject bizContent = JSONObject.parseObject(params.getString("biz_content")); + + String transferId = bizContent.getString("out_biz_no"); + return MutablePair.of(transferId, params); + + } catch (Exception e) { + log.error("error", e); + throw ResponseException.buildText("ERROR"); + } + } + + + @Override + public ChannelRetMsg doNotice(HttpServletRequest request, Object params, TransferOrder transferOrder, MchAppConfigContext mchAppConfigContext) { + + String logPrefix = "【支付宝转账通知】"; + + try { + //配置参数获取 + Byte useCert = null; + String alipaySignType, alipayPublicCert, alipayPublicKey = null; + if(mchAppConfigContext.isIsvsubMch()){ + + // 获取支付参数 + AlipayIsvParams alipayParams = (AlipayIsvParams)configContextQueryService.queryIsvParams(mchAppConfigContext.getMchInfo().getIsvNo(), getIfCode()); + useCert = alipayParams.getUseCert(); + alipaySignType = alipayParams.getSignType(); + alipayPublicCert = alipayParams.getAlipayPublicCert(); + alipayPublicKey = alipayParams.getAlipayPublicKey(); + + }else{ + + // 获取支付参数 + AlipayNormalMchParams alipayParams = (AlipayNormalMchParams)configContextQueryService.queryNormalMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), getIfCode()); + + useCert = alipayParams.getUseCert(); + alipaySignType = alipayParams.getSignType(); + alipayPublicCert = alipayParams.getAlipayPublicCert(); + alipayPublicKey = alipayParams.getAlipayPublicKey(); + } + + // 获取请求参数 + JSONObject jsonParams = (JSONObject) params; + JSONObject bizContent = JSONObject.parseObject(jsonParams.getString("biz_content")); + + boolean verifyResult; + if(useCert != null && useCert == CS.YES){ //证书方式 + + verifyResult = AlipaySignature.rsaCertCheckV1(jsonParams.toJavaObject(Map.class), getCertFilePath(alipayPublicCert), + AlipayConfig.CHARSET, alipaySignType); + + }else{ + verifyResult = AlipaySignature.rsaCheckV1(jsonParams.toJavaObject(Map.class), alipayPublicKey, AlipayConfig.CHARSET, alipaySignType); + } + + //验签失败 + if(!verifyResult){ + log.error("{},验签失败", logPrefix); + throw ResponseException.buildText("ERROR"); + } + + //验签成功后判断上游订单状态 + ResponseEntity okResponse = textResp("SUCCESS"); + + ChannelRetMsg channelRetMsg = new ChannelRetMsg(); + channelRetMsg.setResponseEntity(okResponse); // 响应数据 + + channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.WAITING); // 默认转账中 + + // 成功-SUCCESS + String status = bizContent.getString("status"); + if("SUCCESS".equals(status)){ + channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS); + } + + return channelRetMsg; + } catch (Exception e) { + log.error("error", e); + throw ResponseException.buildText("ERROR"); + } + } + +} diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayTransferService.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayTransferService.java index a96d9f2..325d210 100644 --- a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayTransferService.java +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/alipay/AlipayTransferService.java @@ -104,10 +104,12 @@ public class AlipayTransferService implements ITransferService { if ("SUCCESS".equals(response.getStatus())) { channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS); channelRetMsg.setChannelOrderId(response.getOrderId()); + return channelRetMsg; }else if ("FAIL".equals(response.getStatus())) { channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL); channelRetMsg.setChannelErrCode(AlipayKit.appendErrCode(response.getCode(), response.getSubCode())); channelRetMsg.setChannelErrMsg(AlipayKit.appendErrMsg(response.getMsg(), response.getSubMsg())); + return channelRetMsg; }else { return ChannelRetMsg.waiting(); } @@ -122,9 +124,8 @@ public class AlipayTransferService implements ITransferService { channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL); channelRetMsg.setChannelErrCode(response.getSubCode()); channelRetMsg.setChannelErrMsg(response.getSubMsg()); + return channelRetMsg; } - - return channelRetMsg; } @Override diff --git a/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/transfer/TransferNoticeController.java b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/transfer/TransferNoticeController.java new file mode 100644 index 0000000..e0902d8 --- /dev/null +++ b/jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/transfer/TransferNoticeController.java @@ -0,0 +1,146 @@ +/* + * 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.transfer; + +import com.jeequan.jeepay.core.ctrls.AbstractCtrl; +import com.jeequan.jeepay.core.entity.TransferOrder; +import com.jeequan.jeepay.core.exception.BizException; +import com.jeequan.jeepay.core.exception.ResponseException; +import com.jeequan.jeepay.core.utils.SpringBeansUtil; +import com.jeequan.jeepay.pay.channel.ITransferNoticeService; +import com.jeequan.jeepay.pay.model.MchAppConfigContext; +import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg; +import com.jeequan.jeepay.pay.service.ConfigContextQueryService; +import com.jeequan.jeepay.pay.service.PayMchNotifyService; +import com.jeequan.jeepay.service.impl.TransferOrderService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.MutablePair; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; + +/** +* 转账异步通知入口Controller +* +* @author zx +* @site https://www.jeequan.com +* @date 2022/12/30 10:26 +*/ +@Slf4j +@Controller +public class TransferNoticeController extends AbstractCtrl { + + @Autowired private TransferOrderService transferOrderService; + @Autowired private ConfigContextQueryService configContextQueryService; + @Autowired private PayMchNotifyService payMchNotifyService; + + + /** 异步回调入口 **/ + @ResponseBody + @RequestMapping(value= {"/api/transfer/notify/{ifCode}", "/api/transfer/notify/{ifCode}/{transferId}"}) + public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "transferId", required = false) String urlOrderId){ + + String transferId = null; + String logPrefix = "进入[" +ifCode+ "]转账回调:urlOrderId:["+ StringUtils.defaultIfEmpty(urlOrderId, "") + "] "; + log.info("===== {} =====" , logPrefix); + + try { + + // 参数有误 + if(StringUtils.isEmpty(ifCode)){ + return ResponseEntity.badRequest().body("ifCode is empty"); + } + + //查询转账接口是否存在 + ITransferNoticeService transferNotifyService = SpringBeansUtil.getBean(ifCode + "TransferNoticeService", ITransferNoticeService.class); + + // 支付通道转账接口实现不存在 + if(transferNotifyService == null){ + log.error("{}, transfer interface not exists ", logPrefix); + return ResponseEntity.badRequest().body("[" + ifCode + "] transfer interface not exists"); + } + + // 解析转账单号 和 请求参数 + MutablePair mutablePair = transferNotifyService.parseParams(request, urlOrderId); + if(mutablePair == null){ // 解析数据失败, 响应已处理 + log.error("{}, mutablePair is null ", logPrefix); + throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。 + } + + // 解析到转账单号 + transferId = mutablePair.left; + log.info("{}, 解析数据为:transferId:{}, params:{}", logPrefix, transferId, mutablePair.getRight()); + + // 获取转账单号 和 转账单数据 + TransferOrder transferOrder = transferOrderService.getById(transferId); + + // 转账单不存在 + if(transferOrder == null){ + log.error("{}, 转账单不存在. transferId={} ", logPrefix, transferId); + return transferNotifyService.doNotifyOrderNotExists(request); + } + + //查询出商户应用的配置信息 + MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(transferOrder.getMchNo(), transferOrder.getAppId()); + + //调起接口的回调判断 + ChannelRetMsg notifyResult = transferNotifyService.doNotice(request, mutablePair.getRight(), transferOrder, mchAppConfigContext); + + // 返回null 表明出现异常, 无需处理通知下游等操作。 + if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){ + log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult); + throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。 + } + + // 转账单是 【转账中状态】 + if(transferOrder.getState() == TransferOrder.STATE_ING) { + if(notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) { + // 转账成功 + transferOrderService.updateIng2Success(transferId, notifyResult.getChannelOrderId()); + payMchNotifyService.transferOrderNotify(transferOrderService.getById(transferId)); + + }else if(notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_FAIL){ + // 转账失败 + transferOrderService.updateIng2Fail(transferId, notifyResult.getChannelOrderId(), notifyResult.getChannelUserId(), notifyResult.getChannelErrCode()); + payMchNotifyService.transferOrderNotify(transferOrderService.getById(transferId)); + } + } + + log.info("===== {}, 转账单通知完成。 transferId={}, parseState = {} =====", logPrefix, transferId, notifyResult.getChannelState()); + + return notifyResult.getResponseEntity(); + + } catch (BizException e) { + log.error("{}, transferId={}, BizException", logPrefix, transferId, e); + return ResponseEntity.badRequest().body(e.getMessage()); + + } catch (ResponseException e) { + log.error("{}, transferId={}, ResponseException", logPrefix, transferId, e); + return e.getResponseEntity(); + + } catch (Exception e) { + log.error("{}, transferId={}, 系统异常", logPrefix, transferId, e); + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + +}