新增转账异步通知实现

This commit is contained in:
zhuxiao 2023-02-20 10:23:32 +08:00
parent 43e2669d24
commit d430a2b5ad
5 changed files with 428 additions and 2 deletions

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jeequan.jeepay.pay.channel;
import com.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);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jeequan.jeepay.pay.channel;
import com.jeequan.jeepay.core.entity.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<String, Object> parseParams(HttpServletRequest request, String urlOrderId);
/** 返回需要更新的订单状态 和响应数据 **/
ChannelRetMsg doNotice(HttpServletRequest request,
Object params, TransferOrder transferOrder, MchAppConfigContext mchAppConfigContext);
/** 数据库订单数据不存在 (仅异步通知使用) **/
ResponseEntity doNotifyOrderNotExists(HttpServletRequest request);
}

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jeequan.jeepay.pay.channel.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<String, Object> 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");
}
}
}

View File

@ -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,10 +124,9 @@ public class AlipayTransferService implements ITransferService {
channelRetMsg.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
channelRetMsg.setChannelErrCode(response.getSubCode());
channelRetMsg.setChannelErrMsg(response.getSubMsg());
}
return channelRetMsg;
}
}
@Override
public ChannelRetMsg query(TransferOrder transferOrder, MchAppConfigContext mchAppConfigContext) {

View File

@ -0,0 +1,146 @@
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jeequan.jeepay.pay.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<String, Object> 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());
}
}
}