HandlerFactory
对于NativePayHandler会有不同平台的实现,比如:支付宝、微信,每个平台的接口参数、返回值都不一样,所以是没有办法共用的,只要是每个平台都去编写一个实现类。
那问题来了,我们该如何选择呢?
在这里我们采用了工厂模式进行获取对应的NativePayHandler实例,在不同的渠道实现类中,都指定了@PayChannel注解,通过type属性指定具体的平台(支付宝/微信):
有了这个注解标识后,在HandlerFactory中就可以根据指定的参数获取对应的渠道实现。 核心代码如下:
public static <T> T get(PayChannelEnum payChannel, Class<T> handler) {
Map<String, T> beans = SpringUtil.getBeansOfType(handler);
for (Map.Entry<String, T> entry : beans.entrySet()) {
PayChannel payChannelAnnotation = entry.getValue().getClass().getAnnotation(PayChannel.class);
if (ObjectUtil.isNotEmpty(payChannelAnnotation) && ObjectUtil.equal(payChannel, payChannelAnnotation.type())) {
return entry.getValue();
}
}
return null;
}
使用:
基础服务
在支付宝或微信平台中,支付方式是多种多样的,对于一些服务而言是通用的,比如:查询交易单、退款、查询退款等,所以我们将基于这些通用的接口封装基础服务。
查询交易
用户创建交易后,到底有没有支付成功,还是取消支付,这个可以通过查询交易单接口查询的,支付宝和微信也都提供了这样的接口服务。
Service
在Service中实现了交易单查询的逻辑,代码结构与扫描支付类似。具体与支付平台的对接由BasicPayHandler完成。
@Override
public TradingDTO queryTrading(Long tradingOrderNo) throws SLException {
//通过单号查询交易单数据
TradingEntity trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
//查询前置处理:检测交易单参数
this.beforePayHandler.checkQueryTrading(trading);
String key = TradingCacheConstant.QUERY_PAY + tradingOrderNo;
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//选取不同的支付渠道实现
BasicPayHandler handler = HandlerFactory.get(trading.getTradingChannel(), BasicPayHandler.class);
Boolean result = handler.queryTrading(trading);
if (result) {
//如果交易单已经完成,需要将二维码数据删除,节省数据库空间,如果有需要可以再次生成
if (ObjectUtil.equalsAny(trading.getTradingState(), TradingStateEnum.YJS, TradingStateEnum.QXDD)) {
trading.setQrCode("");
}
//更新数据
this.tradingService.saveOrUpdate(trading);
}
return BeanUtil.toBean(trading, TradingDTO.class);
}
throw new SLException(TradingEnum.NATIVE_QUERY_FAIL);
} catch (SLException e) {
throw e;
} catch (Exception e) {
log.error("查询交易单数据异常: trading = {}", trading, e);
throw new SLException(TradingEnum.NATIVE_QUERY_FAIL);
} finally {
lock.unlock();
}
}
支付宝实现
@Override
public Boolean queryTrading(TradingEntity trading) throws SLException {
//查询配置
Config config = AlipayConfig.getConfig(trading.getEnterpriseId());
//Factory使用配置
Factory.setOptions(config);
AlipayTradeQueryResponse queryResponse;
try {
//调用支付宝API:通用查询支付情况
queryResponse = Factory
.Payment
.Common()
.query(String.valueOf(trading.getTradingOrderNo()));
} catch (Exception e) {
String msg = StrUtil.format("查询支付宝统一下单失败:trading = {}", trading);
log.error(msg, e);
throw new SLException(msg, TradingEnum.NATIVE_QUERY_FAIL.getCode(), TradingEnum.NATIVE_QUERY_FAIL.getStatus());
}
//修改交易单状态
trading.setResultCode(queryResponse.getSubCode());
trading.setResultMsg(queryResponse.getSubMsg());
trading.setResultJson(JSONUtil.toJsonStr(queryResponse));
boolean success = ResponseChecker.success(queryResponse);
//响应成功,分析交易状态
if (success) {
String tradeStatus = queryResponse.getTradeStatus();
if (StrUtil.equals(TradingConstant.ALI_TRADE_CLOSED, tradeStatus)) {
//支付取消:TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)
trading.setTradingState(TradingStateEnum.QXDD);
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.ALI_TRADE_SUCCESS, TradingConstant.ALI_TRADE_FINISHED)) {
// TRADE_SUCCESS(交易支付成功)
// TRADE_FINISHED(交易结束,不可退款)
trading.setTradingState(TradingStateEnum.YJS);
} else {
//非最终状态不处理,当前交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)不处理
return false;
}
return true;
}
throw new SLException(trading.getResultJson(), TradingEnum.NATIVE_QUERY_FAIL.getCode(), TradingEnum.NATIVE_QUERY_FAIL.getStatus());
}
微信支付实现
@Override
public Boolean queryTrading(TradingEntity trading) throws SLException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get(trading.getEnterpriseId());
//请求地址
String apiPath = StrUtil.format("/v3/pay/transactions/out-trade-no/{}", trading.getTradingOrderNo());
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("mchid", client.getMchId())
.build();
WeChatResponse response;
try {
response = client.doGet(apiPath, params);
} catch (Exception e) {
log.error("调用微信接口出错!apiPath = {}, params = {}", apiPath, JSONUtil.toJsonStr(params), e);
throw new SLException(NATIVE_REFUND_FAIL, e);
}
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// 交易状态,枚举值:
// SUCCESS:支付成功
// REFUND:转入退款
// NOTPAY:未支付
// CLOSED:已关闭
// REVOKED:已撤销(仅付款码支付会返回)
// USERPAYING:用户支付中(仅付款码支付会返回)
// PAYERROR:支付失败(仅付款码支付会返回)
String tradeStatus = jsonObject.getStr("trade_state");
if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_TRADE_CLOSED, TradingConstant.WECHAT_TRADE_REVOKED)) {
trading.setTradingState(TradingStateEnum.QXDD);
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_REFUND_SUCCESS, TradingConstant.WECHAT_TRADE_REFUND)) {
trading.setTradingState(TradingStateEnum.YJS);
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_TRADE_NOTPAY)) {
//如果是未支付,需要判断下时间,超过2小时未知的订单需要关闭订单以及设置状态为QXDD
long between = LocalDateTimeUtil.between(trading.getCreated(), LocalDateTimeUtil.now(), ChronoUnit.HOURS);
if (between >= 2) {
return this.closeTrading(trading);
}
} else {
//非最终状态不处理
return false;
}
//修改交易单状态
trading.setResultCode(tradeStatus);
trading.setResultMsg(jsonObject.getStr("trade_state_desc"));
trading.setResultJson(response.getBody());
return true;
}
throw new SLException(response.getBody(), NATIVE_REFUND_FAIL.getCode(), NATIVE_REFUND_FAIL.getCode());
}
退款
Controller
/***
* 统一收单交易退款接口
* 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,
* 将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
* @param tradingOrderNo 交易单号
* @param refundAmount 退款金额
* @return
*/
@PostMapping("refund")
@ApiOperation(value = "统一收单交易退款", notes = "统一收单交易退款")
@ApiImplicitParams({
@ApiImplicitParam(name = "tradingOrderNo", value = "交易单号", required = true),
@ApiImplicitParam(name = "refundAmount", value = "退款金额", required = true)
})
public void refundTrading(@RequestParam("tradingOrderNo") Long tradingOrderNo,
@RequestParam("refundAmount") BigDecimal refundAmount) {
Boolean result = this.basicPayService.refundTrading(tradingOrderNo, refundAmount);
if (!result) {
throw new SLException(TradingEnum.BASIC_REFUND_COUNT_OUT_FAIL);
}
}
Service
@Override
@Transactional
public Boolean refundTrading(Long tradingOrderNo, BigDecimal refundAmount) throws SLException {
//通过单号查询交易单数据
TradingEntity trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
//设置退款金额
trading.setRefund(NumberUtil.add(refundAmount, trading.getRefund()));
//入库前置检查
this.beforePayHandler.checkRefundTrading(trading);
String key = TradingCacheConstant.REFUND_PAY + tradingOrderNo;
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//幂等性的检查
RefundRecordEntity refundRecord = this.beforePayHandler.idempotentRefundTrading(trading, refundAmount);
if (null == refundRecord) {
return false;
}
//选取不同的支付渠道实现
BasicPayHandler handler = HandlerFactory.get(refundRecord.getTradingChannel(), BasicPayHandler.class);
Boolean result = handler.refundTrading(refundRecord);
if (result) {
//更新退款记录数据
this.refundRecordService.saveOrUpdate(refundRecord);
//设置交易单是退款订单
trading.setIsRefund(Constants.YES);
this.tradingService.saveOrUpdate(trading);
}
return true;
}
throw new SLException(TradingEnum.NATIVE_QUERY_FAIL);
} catch (SLException e) {
throw e;
} catch (Exception e) {
log.error("查询交易单数据异常:{}", ExceptionUtil.stacktraceToString(e));
throw new SLException(TradingEnum.NATIVE_QUERY_FAIL);
} finally {
lock.unlock();
}
}
支付宝实现
@Override
public Boolean refundTrading(RefundRecordEntity refundRecord) throws SLException {
//查询配置
Config config = AlipayConfig.getConfig(refundRecord.getEnterpriseId());
//Factory使用配置
Factory.setOptions(config);
//调用支付宝API:通用查询支付情况
AlipayTradeRefundResponse refundResponse;
try {
// 支付宝easy sdk
refundResponse = Factory
.Payment
.Common()
//扩展参数:退款单号
.optional("out_request_no", refundRecord.getRefundNo())
.refund(Convert.toStr(refundRecord.getTradingOrderNo()),
Convert.toStr(refundRecord.getRefundAmount()));
} catch (Exception e) {
String msg = StrUtil.format("调用支付宝退款接口出错!refundRecord = {}", refundRecord);
log.error(msg, e);
throw new SLException(msg, TradingEnum.NATIVE_REFUND_FAIL.getCode(), TradingEnum.NATIVE_REFUND_FAIL.getStatus());
}
refundRecord.setRefundCode(refundResponse.getSubCode());
refundRecord.setRefundMsg(JSONUtil.toJsonStr(refundResponse));
boolean success = ResponseChecker.success(refundResponse);
if (success) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
return true;
}
throw new SLException(refundRecord.getRefundMsg(), TradingEnum.NATIVE_REFUND_FAIL.getCode(), TradingEnum.NATIVE_REFUND_FAIL.getStatus());
}
微信实现
@Override
public Boolean refundTrading(RefundRecordEntity refundRecord) throws SLException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get(refundRecord.getEnterpriseId());
//请求地址
String apiPath = "/v3/refund/domestic/refunds";
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("out_refund_no", Convert.toStr(refundRecord.getRefundNo()))
.put("out_trade_no", Convert.toStr(refundRecord.getTradingOrderNo()))
.put("amount", MapUtil.<String, Object>builder()
.put("refund", NumberUtil.mul(refundRecord.getRefundAmount(), 100)) //本次退款金额
.put("total", NumberUtil.mul(refundRecord.getTotal(), 100)) //原订单金额
.put("currency", "CNY") //币种
.build())
.build();
WeChatResponse response;
try {
response = client.doPost(apiPath, params);
} catch (Exception e) {
log.error("调用微信接口出错!apiPath = {}, params = {}", apiPath, JSONUtil.toJsonStr(params), e);
throw new SLException(NATIVE_REFUND_FAIL, e);
}
refundRecord.setRefundCode(Convert.toStr(response.getStatus()));
refundRecord.setRefundMsg(response.getBody());
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// SUCCESS:退款成功
// CLOSED:退款关闭
// PROCESSING:退款处理中
// ABNORMAL:退款异常
String status = jsonObject.getStr("status");
if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_PROCESSING)) {
refundRecord.setRefundStatus(RefundStatusEnum.SENDING);
} else if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_SUCCESS)) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
} else {
refundRecord.setRefundStatus(RefundStatusEnum.FAIL);
}
return true;
}
throw new SLException(refundRecord.getRefundMsg(), NATIVE_REFUND_FAIL.getCode(), NATIVE_REFUND_FAIL.getStatus());
}
查询退款
Controller
/***
* 统一收单交易退款查询接口
* @param refundNo 退款交易单号
* @return
*/
@PostMapping("refund/{refundNo}")
@ApiOperation(value = "查询统一收单交易退款", notes = "查询统一收单交易退款")
@ApiImplicitParam(name = "refundNo", value = "退款交易单", required = true)
public RefundRecordDTO queryRefundDownLineTrading(@PathVariable("refundNo") Long refundNo) {
return this.basicPayService.queryRefundTrading(refundNo);
}
Service
@Override
public RefundRecordDTO queryRefundTrading(Long refundNo) throws SLException {
//通过单号查询交易单数据
RefundRecordEntity refundRecord = this.refundRecordService.findByRefundNo(refundNo);
//查询前置处理
this.beforePayHandler.checkQueryRefundTrading(refundRecord);
String key = TradingCacheConstant.REFUND_QUERY_PAY + refundNo;
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//选取不同的支付渠道实现
BasicPayHandler handler = HandlerFactory.get(refundRecord.getTradingChannel(), BasicPayHandler.class);
Boolean result = handler.queryRefundTrading(refundRecord);
if (result) {
//更新数据
this.refundRecordService.saveOrUpdate(refundRecord);
}
return BeanUtil.toBean(refundRecord, RefundRecordDTO.class);
}
throw new SLException(TradingEnum.REFUND_FAIL);
} catch (SLException e) {
throw e;
} catch (Exception e) {
log.error("查询退款交易单数据异常: refundRecord = {}", refundRecord, e);
throw new SLException(TradingEnum.REFUND_FAIL);
} finally {
lock.unlock();
}
}
支付宝实现
@Override
public Boolean queryRefundTrading(RefundRecordEntity refundRecord) throws SLException {
//查询配置
Config config = AlipayConfig.getConfig(refundRecord.getEnterpriseId());
//Factory使用配置
Factory.setOptions(config);
AlipayTradeFastpayRefundQueryResponse response;
try {
response = Factory.Payment.Common().queryRefund(
Convert.toStr(refundRecord.getTradingOrderNo()),
Convert.toStr(refundRecord.getRefundNo()));
} catch (Exception e) {
log.error("调用支付宝查询退款接口出错!refundRecord = {}", refundRecord, e);
throw new SLException(TradingEnum.NATIVE_REFUND_FAIL, e);
}
refundRecord.setRefundCode(response.getSubCode());
refundRecord.setRefundMsg(JSONUtil.toJsonStr(response));
boolean success = ResponseChecker.success(response);
if (success) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
return true;
}
throw new SLException(refundRecord.getRefundMsg(), TradingEnum.NATIVE_REFUND_FAIL.getCode(), TradingEnum.NATIVE_REFUND_FAIL.getStatus());
}
微信支付实现
@Override
public Boolean queryRefundTrading(RefundRecordEntity refundRecord) throws SLException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get(refundRecord.getEnterpriseId());
//请求地址
String apiPath = StrUtil.format("/v3/refund/domestic/refunds/{}", refundRecord.getRefundNo());
WeChatResponse response;
try {
response = client.doGet(apiPath);
} catch (Exception e) {
log.error("调用微信接口出错!apiPath = {}", apiPath, e);
throw new SLException(NATIVE_QUERY_REFUND_FAIL, e);
}
refundRecord.setRefundCode(Convert.toStr(response.getStatus()));
refundRecord.setRefundMsg(response.getBody());
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// SUCCESS:退款成功
// CLOSED:退款关闭
// PROCESSING:退款处理中
// ABNORMAL:退款异常
String status = jsonObject.getStr("status");
if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_PROCESSING)) {
refundRecord.setRefundStatus(RefundStatusEnum.SENDING);
} else if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_SUCCESS)) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
} else {
refundRecord.setRefundStatus(RefundStatusEnum.FAIL);
}
return true;
}
throw new SLException(response.getBody(), NATIVE_QUERY_REFUND_FAIL.getCode(), NATIVE_QUERY_REFUND_FAIL.getStatus());
}
同步支付状态
在支付平台创建交易单后,如果用户支付成功,我们怎么知道支付成功了呢?一般的做法有两种,分别是【异步通知】和【主动查询】,基本的流程如下:
说明:
● 在用户支付成功后,【步骤4】支付平台会通知【支付微服务】,这个就是异步通知,需要在【支付微服务】中对外暴露接口
● 由于网络的不确定性,异步通知可能出现故障【步骤6】
● 支付微服务中需要有定时任务,查询正在支付中的订单的状态
● 可以看出【异步通知】与【主动定时查询】这两种方式是互不的,缺一不可。
异步通知
支付宝和微信都提供了异步通知功能,具体参考官方文档:
NotifyController
/**
* 支付结果的通知
*/
@RestController
@Api(tags = "支付通知")
@RequestMapping("notify")
public class NotifyController {
@Resource
private NotifyService notifyService;
/**
* 微信支付成功回调(成功后无需响应内容)
*
* @param httpEntity 微信请求信息
* @param enterpriseId 商户id
* @return 正常响应200,否则响应500
*/
@PostMapping("wx/{enterpriseId}")
public ResponseEntity<Object> wxPayNotify(HttpEntity<String> httpEntity, @PathVariable("enterpriseId") Long enterpriseId) {
try {
//获取请求头
HttpHeaders headers = httpEntity.getHeaders();
//构建微信请求数据对象
NotificationRequest request = new NotificationRequest.Builder()
.withSerialNumber(headers.getFirst("Wechatpay-Serial")) //证书序列号(微信平台)
.withNonce(headers.getFirst("Wechatpay-Nonce")) //随机串
.withTimestamp(headers.getFirst("Wechatpay-Timestamp")) //时间戳
.withSignature(headers.getFirst("Wechatpay-Signature")) //签名字符串
.withBody(httpEntity.getBody())
.build();
//微信通知的业务处理
this.notifyService.wxPayNotify(request, enterpriseId);
} catch (SLException e) {
Map<String, Object> result = MapUtil.<String, Object>builder()
.put("code", "FAIL")
.put("message", e.getMsg())
.build();
//响应500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
return ResponseEntity.ok(null);
}
/**
* 支付宝支付成功回调(成功后需要响应success)
*
* @param enterpriseId 商户id
* @return 正常响应200,否则响应500
*/
@PostMapping("alipay/{enterpriseId}")
public ResponseEntity<String> aliPayNotify(HttpServletRequest request,
@PathVariable("enterpriseId") Long enterpriseId) {
try {
//支付宝通知的业务处理
this.notifyService.aliPayNotify(request, enterpriseId);
} catch (SLException e) {
//响应500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return ResponseEntity.ok("success");
}
}
异步通知debug测试时,三方支付平台会发起多个重试请求,会导致debug无法拦截每个请求,需要将debug模式设置成单线程模式,如下:(断点红球上点右键进行设置)
NotifyService
/**
* 支付通知
*/
public interface NotifyService {
/**
* 微信支付通知,官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml
*
* @param request 微信请求对象
* @param enterpriseId 商户id
* @throws SLException 抛出SL异常,通过异常决定是否响应200
*/
void wxPayNotify(NotificationRequest request, Long enterpriseId) throws SLException;
/**
* 支付宝支付通知,官方文档:https://opendocs.alipay.com/open/194/103296?ref=api
*
* @param request 请求对象
* @param enterpriseId 商户id
* @throws SLException 抛出SL异常,通过异常决定是否响应200
*/
void aliPayNotify(HttpServletRequest request, Long enterpriseId) throws SLException;
}
NotifyServiceImpl
注意: ● 支付成功的通知请求,一定要确保是真正来自支付平台,防止伪造请求造成数据错误,导致财产损失
● 对于响应会数据需要进行解密处理
/**
* 支付成功的通知处理
*/
@Slf4j
@Service
public class NotifyServiceImpl implements NotifyService {
@Resource
private TradingService tradingService;
@Resource
private RedissonClient redissonClient;
@Resource
private MQFeign mqFeign;
@Override
public void wxPayNotify(NotificationRequest request, Long enterpriseId) throws SLException {
// 查询配置
WechatPayHttpClient client = WechatPayHttpClient.get(enterpriseId);
JSONObject jsonData;
//验证签名,确保请求来自微信
try {
//确保在管理器中存在自动更新的商户证书
client.createHttpClient();
CertificatesManager certificatesManager = CertificatesManager.getInstance();
Verifier verifier = certificatesManager.getVerifier(client.getMchId());
//验签和解析请求数据
NotificationHandler notificationHandler = new NotificationHandler(verifier, client.getApiV3Key().getBytes(StandardCharsets.UTF_8));
Notification notification = notificationHandler.parse(request);
if (!StrUtil.equals("TRANSACTION.SUCCESS", notification.getEventType())) {
//非成功请求直接返回,理论上都是成功的请求
return;
}
//获取解密后的数据
jsonData = JSONUtil.parseObj(notification.getDecryptData());
} catch (Exception e) {
throw new SLException("验签失败");
}
if (!StrUtil.equals(jsonData.getStr("trade_state"), TradingConstant.WECHAT_TRADE_SUCCESS)) {
return;
}
//交易单号
Long tradingOrderNo = jsonData.getLong("out_trade_no");
log.info("微信支付通知:tradingOrderNo = {}, data = {}", tradingOrderNo, jsonData);
//更新交易单
this.updateTrading(tradingOrderNo, jsonData.getStr("trade_state_desc"), jsonData.toString());
}
private void updateTrading(Long tradingOrderNo, String resultMsg, String resultJson) {
String key = TradingCacheConstant.CREATE_PAY + tradingOrderNo;
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
TradingEntity trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
if (trading.getTradingState() == TradingStateEnum.YJS) {
// 已付款
return;
}
//设置成付款成功
trading.setTradingState(TradingStateEnum.YJS);
//清空二维码数据
trading.setQrCode("");
trading.setResultMsg(resultMsg);
trading.setResultJson(resultJson);
this.tradingService.saveOrUpdate(trading);
// 发消息通知其他系统支付成功
TradeStatusMsg tradeStatusMsg = TradeStatusMsg.builder()
.tradingOrderNo(trading.getTradingOrderNo())
.productOrderNo(trading.getProductOrderNo())
.statusCode(TradingStateEnum.YJS.getCode())
.statusName(TradingStateEnum.YJS.name())
.build();
String msg = JSONUtil.toJsonStr(Collections.singletonList(tradeStatusMsg));
this.mqFeign.sendMsg(Constants.MQ.Exchanges.TRADE, Constants.MQ.RoutingKeys.TRADE_UPDATE_STATUS, msg);
return;
}
} catch (Exception e) {
throw new SLException("处理业务失败");
} finally {
lock.unlock();
}
throw new SLException("处理业务失败");
}
@Override
public void aliPayNotify(HttpServletRequest request, Long enterpriseId) throws SLException {
//获取参数
Map<String, String[]> parameterMap = request.getParameterMap();
Map<String, String> param = new HashMap<>();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
param.put(entry.getKey(), StrUtil.join(",", entry.getValue()));
}
String tradeStatus = param.get("trade_status");
if (!StrUtil.equals(tradeStatus, TradingConstant.ALI_TRADE_SUCCESS)) {
return;
}
//查询配置
Config config = AlipayConfig.getConfig(enterpriseId);
Factory.setOptions(config);
try {
Boolean result = Factory
.Payment
.Common().verifyNotify(param);
if (!result) {
throw new SLException("验签失败");
}
} catch (Exception e) {
throw new SLException("验签失败");
}
//获取交易单号
Long tradingOrderNo = Convert.toLong(param.get("out_trade_no"));
//更新交易单
this.updateTrading(tradingOrderNo, "支付成功", JSONUtil.toJsonStr(param));
}
}
xxl-job调度流程
TradeJob
在此任务中包含两个任务,一个是查询支付状态,另一个是查询退款状态。
/**
* 交易任务,主要是查询订单的支付状态 和 退款的成功状态
*/
@Slf4j
@Component
public class TradeJob {
@Value("${sl.job.trading.count:100}")
private Integer tradingCount;
@Value("${sl.job.refund.count:100}")
private Integer refundCount;
@Resource
private TradingService tradingService;
@Resource
private RefundRecordService refundRecordService;
@Resource
private BasicPayService basicPayService;
@Resource
private MQFeign mqFeign;
/**
* 分片广播方式查询支付状态
* 逻辑:每次最多查询{tradingCount}个未完成的交易单,交易单id与shardTotal取模,值等于shardIndex进行处理
*/
@XxlJob("tradingJob")
public void tradingJob() {
// 分片参数
int shardIndex = NumberUtil.max(XxlJobHelper.getShardIndex(), 0);
int shardTotal = NumberUtil.max(XxlJobHelper.getShardTotal(), 1);
List<TradingEntity> list = this.tradingService.findListByTradingState(TradingStateEnum.FKZ, tradingCount);
if (CollUtil.isEmpty(list)) {
XxlJobHelper.log("查询到交易单列表为空!shardIndex = {}, shardTotal = {}", shardIndex, shardTotal);
return;
}
//定义消息通知列表,只要是状态不为【付款中】就需要通知其他系统
List<TradeStatusMsg> tradeMsgList = new ArrayList<>();
for (TradingEntity trading : list) {
if (trading.getTradingOrderNo() % shardTotal != shardIndex) {
continue;
}
try {
//查询交易单
TradingDTO tradingDTO = this.basicPayService.queryTrading(trading.getTradingOrderNo());
if (TradingStateEnum.FKZ != tradingDTO.getTradingState()) {
TradeStatusMsg tradeStatusMsg = TradeStatusMsg.builder()
.tradingOrderNo(trading.getTradingOrderNo())
.productOrderNo(trading.getProductOrderNo())
.statusCode(tradingDTO.getTradingState().getCode())
.statusName(tradingDTO.getTradingState().name())
.build();
tradeMsgList.add(tradeStatusMsg);
}
} catch (Exception e) {
XxlJobHelper.log("查询交易单出错!shardIndex = {}, shardTotal = {}, trading = {}", shardIndex, shardTotal, trading, e);
}
}
if (CollUtil.isEmpty(tradeMsgList)) {
return;
}
//发送消息通知其他系统
String msg = JSONUtil.toJsonStr(tradeMsgList);
this.mqFeign.sendMsg(Constants.MQ.Exchanges.TRADE, Constants.MQ.RoutingKeys.TRADE_UPDATE_STATUS, msg);
}
/**
* 分片广播方式查询退款状态
*/
@XxlJob("refundJob")
public void refundJob() {
// 分片参数
int shardIndex = NumberUtil.max(XxlJobHelper.getShardIndex(), 0);
int shardTotal = NumberUtil.max(XxlJobHelper.getShardTotal(), 1);
List<RefundRecordEntity> list = this.refundRecordService.findListByRefundStatus(RefundStatusEnum.SENDING, refundCount);
if (CollUtil.isEmpty(list)) {
XxlJobHelper.log("查询到退款单列表为空!shardIndex = {}, shardTotal = {}", shardIndex, shardTotal);
return;
}
//定义消息通知列表,只要是状态不为【退款中】就需要通知其他系统
List<TradeStatusMsg> tradeMsgList = new ArrayList<>();
for (RefundRecordEntity refundRecord : list) {
if (refundRecord.getRefundNo() % shardTotal != shardIndex) {
continue;
}
try {
//查询退款单
RefundRecordDTO refundRecordDTO = this.basicPayService.queryRefundTrading(refundRecord.getRefundNo());
if (RefundStatusEnum.SENDING != refundRecordDTO.getRefundStatus()) {
TradeStatusMsg tradeStatusMsg = TradeStatusMsg.builder()
.tradingOrderNo(refundRecord.getTradingOrderNo())
.productOrderNo(refundRecord.getProductOrderNo())
.refundNo(refundRecord.getRefundNo())
.statusCode(refundRecord.getRefundStatus().getCode())
.statusName(refundRecord.getRefundStatus().name())
.build();
tradeMsgList.add(tradeStatusMsg);
}
} catch (Exception e) {
XxlJobHelper.log("查询退款单出错!shardIndex = {}, shardTotal = {}, refundRecord = {}", shardIndex, shardTotal, refundRecord, e);
}
}
if (CollUtil.isEmpty(tradeMsgList)) {
return;
}
//发送消息通知其他系统
String msg = JSONUtil.toJsonStr(tradeMsgList);
this.mqFeign.sendMsg(Constants.MQ.Exchanges.TRADE, Constants.MQ.RoutingKeys.REFUND_UPDATE_STATUS, msg);
}
}
评论( 0 )