支払い後、再度支払いが成功したが、支払いが行われていないと表示される場合はどうすればよいですか? バックエンド コード ロジックとは何ですか? 【杭州マルチテスター_王先生】【杭州マルチテスター】...

取り下げられた注文とは何ですか?
いわゆる注文紛失とは、ユーザーが注文して支払いを済ませ、ウォレットで支払いを完了してから、EC アプリに戻って注文がまだ未払いであることを確認することを意味します。爆発し、結果は苦情または否定的なレビューになります

ユーザーはだまされたと感じています
が、注文の減少はどのように発生したのでしょうか? 注文の支払いの完全なプロセスを見てみましょう。

ウォレット決済の完全なプロセス
ユーザーが電子商取引アプリケーションから決済をクリックすると、クライアントがサーバーへの決済要求を開始します. 決済サービスが
サードパーティの決済チャネルへの決済を開始し、決済チャネルがそれに応答します.対応するURL.
APPを例にとると、クライアントは通常、対応するウォレットをプルアップし、ユーザーは対応するウォレットにジャンプします. ユーザーはウォレットで
支払いを完了します. ユーザーが支払いを
完了した後、ユーザーはジャンプして元に戻ります.対応する e コマース APP
クライアントは、注文サービスをポーリングして注文ステータスを取得します.
支払いチャネルは、支払いサービスにコールバックし、支払い結果を通知します
. 支払いサービスは、注文サービスに注文ステータスを更新するよう通知します.
支払い注文の場合、それはできます以下の状態に大別されます。

支払いステータス
未払い: ユーザーがクリックして支払いを行った後、支払いサービスが支払いチャネルを要求する前は、ユーザーは未払いの状態になります. 支払い: ユーザーが支払いを開始した後、支払い
ウォレットにジャンプし、支払いを完了します.この状態は霧のような状態と言えます. 電子商取引システムは、ユーザーの支払いの成功/
失敗/キャンセル/終了について確信が持てません: 電子商取引システムは、サードパーティのウォレットでユーザーの支払いを最終的に決定します.最終結果は
問題ないようですが、なぜ注文を取り下げたのですか?簡単に言えば、支払いのステータスが同期されていないか、同期されていません。

オーダードロップが発生
1.ペイメントチャネルのペイメントコールバックで
何らかの例外が発生し、ペイメントサービスがペイメント
チャネルからのコールバック通知を受信できなかった. オーダーステータスの取得クライアントは通常、ステータスを取得するためにポーリングします. 取得できない場合があります.ポーリング時間内の注文ステータス. その結果、ユーザーは注文が支払われていないことがわかります. 1は外部注文と呼ぶことができ、2と3は内部注文と呼ぶことができます. 1. 次に、注文が落ちる問題を回避する方法を見てみましょう。内部注文損失を防ぐ方法 まずシステム内の注文損失から始めましょう. もちろん、システム内では、安定性を確保しやすく、注文損失の可能性は比較的小さくなります. 決済サービスとサーバー側の注文サービスとの間で注文の取りこぼしを防ぐには、決済通知注文の決済結果をできるだけ成功させることがポイントであり、通常はこの2つの方法を使用します。






サーバー側でのオーダー ドロップの防止1. 同期コール リトライ メカニズム ペイメント
サービスがオーダー サービスをコールする場合、ネットワーク ジッタによるコールの失敗を防ぐために、失敗時に再試行する必要があります。2. 非同期メッセージ配信の信頼性
同期は安全ではありません。別の非同期を追加します。支払いサービスは支払い成功メッセージを配信し、注文サービスは支払い成功メッセージを消費します. プロセス全体で可能な限り信頼性を確保する必要があります. たとえば, 注文サービスは注文ステータスの更新が完了した後にメッセージの消費を完了する必要があります.
同期+非同期の両手戦略は、基本的にサーバーからの内部注文を防ぐことができます。
一貫した状態を確保するための分散トランザクション (トランザクション メッセージ、Seata) の導入については、必要ないと思います。

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

おすすめ

転載: blog.csdn.net/weixin_39362573/article/details/128946243