¿Qué debo hacer si el pago vuelve a ser exitoso después de pagar pero muestra que el pago no se ha realizado? ¿Cuál es la lógica del código de fondo? 【Hangzhou multiprobador_Wang señor】【Hangzhou multiprobador】...

¿Qué es un pedido caído?
El llamado pedido perdido significa que el usuario realiza un pedido y paga, completa el pago en la billetera y luego regresa a la aplicación de comercio electrónico para ver que el pedido aún no se ha pagado... No hay duda de que el usuario explotará y el resultado será una queja o una crítica negativa
.
El usuario siente que ha sido engañado,
entonces, ¿cómo vino la caída del pedido? Echemos un vistazo al proceso completo de pago del pedido:

El proceso completo de pago de la billetera
El usuario hace clic en el pago desde la aplicación de comercio electrónico y el cliente inicia una solicitud de pago al servidor. El
servicio de pago iniciará el pago al canal de pago de terceros y el canal de pago responderá a la url correspondiente. Tomando
la aplicación como ejemplo, el cliente generalmente abrirá la billetera correspondiente, y el usuario salta a la billetera correspondiente. El usuario
completa el pago en la billetera.
Después de que el usuario completa el pago, el usuario salta de nuevo a el cliente de la aplicación de comercio electrónico correspondiente
para sondear el servicio de pedidos para obtener el estado del pedido.
El canal de pago devuelve la llamada al servicio de pago y notifica el resultado del pago.
El servicio de pago notifica al servicio de pedidos para actualizar el estado del pedido.
Para pedidos de pago, puede dividirse aproximadamente en los siguientes estados:

Estado de pago
No pagado: después de que el usuario hace clic para pagar, pero antes de que el servicio de pago solicite el canal de pago, el usuario se encuentra en estado no pagado
Pago: después de que el usuario inicia el pago, salta a la billetera de pago y luego completa el pago. En este estado, se puede decir que es un estado de niebla. El sistema de comercio electrónico no está seguro del éxito/fallo/cancelación/cierre del pago del usuario
: el sistema de comercio electrónico finalmente determina el pago del usuario en la billetera de terceros. El resultado final
parece no ser un problema. ¿Por qué cancelaste el pedido? En pocas palabras, el estado del pago no está sincronizado o no está sincronizado en el tiempo.

Se produjo una caída del pedido
1.
Ocurrieron algunas excepciones en la devolución de llamada de pago del canal de pago, lo que provocó que el servicio de pago no recibiera la notificación de devolución de llamada del
canal de pago Obtención del estado del pedido El cliente generalmente realiza una encuesta para obtener el estado. el estado del pedido dentro del tiempo de sondeo. Como resultado, el usuario ve que el pedido no está pagado. 1 se puede llamar un pedido externo, y 2 y 3 se pueden llamar un pedido interno. uno. A continuación, veamos cómo evitar el problema de la caída de pedidos. Cómo prevenir la pérdida de pedidos internos Comencemos con la pérdida de pedidos dentro del sistema. Por supuesto, dentro del sistema, la estabilidad es más fácil de garantizar y la probabilidad de pérdida de pedidos es relativamente pequeña. La clave para evitar la pérdida de pedidos entre el servicio de pago y el servicio de pedidos en el lado del servidor es asegurarse de que el resultado del pago del pedido de notificación de pago sea lo más exitoso posible. Generalmente usamos estos dos métodos.






Evitar la caída del pedido en el lado del servidor 1. Mecanismo de reintento de llamada síncrono
Cuando el servicio de pago llama al servicio de pedido, debe volver a intentarlo si falla para evitar que la llamada falle debido a la fluctuación de la red. 2. Confiabilidad de la entrega de mensajes asincrónicos
La sincronización no es segura, luego agregue otro asíncrono. El servicio de pago entrega un mensaje de éxito de pago, y el servicio de pedidos consume el mensaje de éxito de pago. Todo el proceso debe garantizar la confiabilidad tanto como sea posible. Por ejemplo, el servicio de pedidos debe completar el consumo de mensajes después de completar la actualización del estado del pedido.
La estrategia a dos manos síncrona + asíncrona básicamente puede evitar órdenes internas del servidor.
En cuanto a la introducción de transacciones distribuidas (mensajes de transacción, Seata) para garantizar un estado consistente, no creo que sea necesario.

客户端如何防止掉单
用户支付完成后,跳回电商系统,客户端会轮询一下订单的状态,通常两三秒内,就会得到订单完成支付的结果,这个过程出现问题的概率相比是非常低的。
但是也不排除,很小概率下,客户端轮询一段时间,还没得到结果,那么只能结束轮询,给用户展示未支付。
这种情况,通常问题也是出在服务端,没有及时更新订单的状态,最主要的还是要处理服务端的掉单,保证服务端能及时同步支付订单的状态。
但是一旦服务端的订单状态变更了,也要尽可能同步到客户端,不能让用户一直看到未支付。
客户端和服务端之间,同步状态,无非就是推和拉:1.  客户端轮询
客户端判断用户未支付之后,通常会进行订单倒计时。

倒计时
这里再提一下?大家觉得这种倒计时是怎么实现的呢?纯客户端组组件倒计时吗?
——肯定不行,通常是客户端组件倒计时,定期向服务端请求,检查倒计时时间。同样的,这种情况下,客户端也可以检查支付状态。2.  服务端推送
说真的,服务端推送,看上去是一种很美好的方案,Web端可以使用Websocket,APP端可以用自定义Push,大家可以看看7种实现web实时消息推送的方案。但实际上,推送的成功率经常不那么理想。怎么防止外部掉单
相比较内部掉单,外部掉单发生的概率就大很多,毕竟和外部渠道的对接,不可控的因素更多。
要防止外部掉单,核心就是四个字:“主动查询”,如果只是等待第三方的回调通知,风险还是比较大的,支付服务要主动向第三方查询支付状态,即使有什么异常,也能及时感知到。
主动查询,主要就是两种形式:定时任务查询
毫无疑问,最简单的肯定就是定时任务了,支付服务,定时查询一段时间内支付中的支付订单,向第三方渠道查询支付结果,查询到终态之后,就去更新支付订单状态、通知订单服务:

定时查询支付状态
实现也很简单,用xxl-job之类的定时任务框架,定时扫表,向第三方查询就行了,大概代码如下:

@XxlJob("syncPaymentResult")
public ReturnT<String> syncPaymentResult(int hour) {
    //……
    //查询一段之间支付中的流水
    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;
}

定时任务的最大好处肯定是简单了,但是它也有一些问题:1.  查询的结果不实时
定时任务频率的设置永远是个不好确定的事情,间隔短对数据库压力大,间隔长了不实时,很容易出现,上面提到的用户回到APP,结果轮询不到支付成功状态的情况。
实际上,用户跳转钱包之后,通常会很快完成支付,如果短时间内没有完成支付,那么一般也不会再付了。所以其实,发起支付开始,从第三方查询支付结果的频率应该是递减的。2.  对数据库有压力
定时任务扫表,对数据库肯定是会有压力的,扫表的时候,经常会看到数据库的监控出现一个小突刺,如果数据量大的话,可能影响更大。
可以单独创建一个支付中流水表,定时任务扫描这张表,获取到支付最终态之后,就删除掉对应的记录。延时消息查询
定时任务存在一些问题,那么有没有什么其它办法呢?答案是延时消息。

延时消息查询支付状态
在发起支付之后,发送一个延时消息,前面讲到,用户跳转到钱包,通常很快会支付,所以我们希望查询支付状态这个步骤,符合这个规律,所以希望在10s、30s、1min、1min30s、2min、5min、7min……这种频率去查询支付订单的状态,这里我们可以用一个队列结构实现,队列里存放下一次查询的时间间隔。
大概代码如下:

//……
//控制查询频率的队列,时间单位为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,所以看起来不太复杂,但是如果用开源方案,那就没那么简单。
充钱就能解决
时效性更好
无需扫表,对数据库压力较小结语
这篇文章介绍了一个让用户炸毛,让客服恼火,让开发挠头的问题——掉单,包括为什么会掉单,怎么防止掉单。
其中内部掉单,发生的概率相对较少,掉单最主要的原因还是所谓的外部掉单。
外部掉单解决的关键点是主动查询,有两种常用的方案:定时任务查询和延时消息查询,前者简单一些,后者功能上更加出色。


原文链接转载于:https://blog.csdn.net/m0_73311735/article/details/126661708 

参考文章:https://blog.csdn.net/July_whj/article/details/126819380

Supongo que te gusta

Origin blog.csdn.net/weixin_39362573/article/details/128946243
Recomendado
Clasificación