支付成功/失败/取消/关闭:电商系统最终确定了用户在第三方钱包的支付最终结果
客户端通常是轮询获取状态,可能会在轮询时间内没有获取到订单状态,结果用户看到未支付
毫无疑问,最简单的肯定就是定时任务了,支付服务,定时查询一段时间内支付中的支付订单,向第三方渠道查询支付结果,查询到终态之后,就去更新支付订单状态、通知订单服务:
实现也很简单,用xxl-job之类的定时任务框架,定时扫表,向第三方查询就行了,大概代码如下:
@XxlJob("syncPaymentResult") public ReturnT<String> syncPaymentResult(int hour) { //堆代码 duidaima.com //查询一段之间支付中的流水 List<PayDO> pendingList = payMapper.getPending(now.minusHours(hour)); for (PayDO payDO : pendingList) { //…… // 主动去第三方查 PaymentStatusResult paymentStatusResult = paymentService.getPaymentStatus(paymentId); // 第三方支付中 if (PaymentStatusEnum.PENDING.equals(paymentStatusResult.getPayStatus())) { continue; } //支付完成,获取到终态 //…… // 1.更新流水 payMapper.updatePayDO(payDO); // 2.通知订单服务 orderService.notifyOrder(notifyLocalRequestVO); } return ReturnT.SUCCESS; }定时任务的最大好处肯定是简单了,但是它也有一些问题:
//…… //控制查询频率的队列,时间单位为s Deque<Integer> queue = new LinkedList<>(); queue.offer(10); queue.offer(30); queue.offer(60); //…… //支付订单号 PaymentConsultDTO paymentConsultDTO = new PaymentConsultDTO(); paymentConsultDTO.setPaymentId(paymentId); paymentConsultDTO.setIntervalQueue(queue); //发送延时消息 Message message = new Message(); message.setTopic("PAYMENT"); message.setKey(paymentId); message.setTag("CONSULT"); message.setBody(toJSONString(paymentConsultDTO).getBytes(StandardCharsets.UTF_8)); try { //第一个延时消息,延时10s long delayTime = System.currentTimeMillis() + 10 * 1000; // 设置消息需要被投递的时间。 message.setStartDeliverTime(delayTime); SendResult sendResult = producer.send(message); //…… } catch (Throwable th) { log.error("[sendMessage] error:", th); }PS:这里用的是RocketMQ云服务器版,支持任意级别的延时消息,开源版的RocketMQ只支持固定级别的延时消息,不得不感慨充钱才能变强。有实力的开发团队,可以在开源基础上,进行二次开发。
@Component @Slf4j public class ConsultListener implements MessageListener { //消费者注册,监听器注册 //…… @Override public Action consume(Message message, ConsumeContext context) { // UTF-8解析 String body = new String(message.getBody(), StandardCharsets.UTF_8); PaymentConsultDTO paymentConsultDTO= JsonUtil.parseObject(body, new TypeReference<PaymentConsultDTO>() { }); if (paymentConsultDTO == null) { return Action.ReconsumeLater; } //获取支付流水 PayDO payDO=payMapper.selectById(paymentConsultDTO.getPaymentId()); //…… //查询支付状态 PaymentStatusResult paymentStatusResult=payService.getPaymentStatus(paymentStatusContext); //还在支付中,继续投递一个延时消息 if (PaymentStatusEnum.PENDING.equals(paymentStatusResult.getPayStatus())){ //发送延时消息 Message msg = new Message(); message.setTopic("PAYMENT"); message.setKey(paymentConsultDTO.getPaymentId()); message.setTag("CONSULT"); //下一个延时消息的频率 Long delaySeconds=paymentConsultDTO.getIntervalQueue().poll(); message.setBody(toJSONString(paymentConsultDTO).getBytes(StandardCharsets.UTF_8)); try { Long delayTime = System.currentTimeMillis() + delaySeconds * 1000; // 设置消息需要被投递的时间。 message.setStartDeliverTime(delayTime); SendResult sendResult = producer.send(message); //…… } catch (Throwable th) { log.error("[sendMessage] error:", th); } return Action.CommitMessage; } //获取到最终态 //更新支付订单状态 //…… //通知订单服务 //…… return Action.CommitMessage; } }延时消息的方案相对于定时轮询方案来讲:不过大家也看到,我这里的实现是利用的是充钱版的RocketMQ,所以看起来不太复杂,但是如果用开源方案,那就没那么简单。