做过税地系统或三方支付或对接过银行支付通道的朋友应该清楚,我们的支付系统在调用银行通道获取到付款单的终态后,涉及到记账、结算、通知下游商户等业务逻辑。这其中,有一项默认的操作是,更新付款单的状态。
并且,应该先变更状态,变更状态成功后,然后再去执行其他业务逻辑。
我们在参与一次代码评审时,就发现了不靠谱的事情。开发人员先发起异步记账,然后才是更新付款单状态。
/** * 订单完成的业务逻辑封装 * @param paymentOrder 付款单对象 * @param payResult 付款结果 */ void handlePaymentCompletion(PaymentOrder paymentOrder, PaymentResult payResult){ if (PaymentOrderStatusEnum.isFinalState(payResult.getStatus())){ return; } // 订单完成,异步记账 accounting(paymentOrder); // 持久化更新订单付款状态 updateOrderPayResult(paymentOrder, payResult); // 异步通知下游商户系统 notifyMerchant(paymentOrder); }
那么,这会出现什么后果呢?
假如持久化记账完成了,商户的账户余额也变更了,但是,变更付款单状态时,由于字段超长等某些原因导致异常,程序中断,付款单状态未能持久化变更,也就是说,此时数据库里付款单的状态依然是“付款中”。那么,定时查单任务(或查单延迟消息队列)在下一次调度时还会读取到这一笔“付款中”的交易,然后继续查询银行通道,通道返回终态后,又开始执行记账逻辑,这为重复记账埋下了种子。
重复记账会产生什么后果呢?
①如果付款单是付款失败,商户账户可用余额会增加。重复记账,就会出现余额异常翻倍增加,意味着商户可以用增加的这笔“意外之财”继续付款。这是资金风险的原罪。
②如果付款单是付款成功,商户账户可用余额减少。重复记账,就会导致商户余额异常成倍减少,意味着商户无法继续付款。我(商户)明明给你充钱了,你却告诉我余额不足,不让我用,我投诉你!
因此,这个顺序颠倒的代码逻辑是不是很可怕?支付系统最怕这种资金风险。上面说的定时任务只是一方面,回调或并发场景下也同样会导致这个资金隐患。
借助这个案例,来强调一下,业务处理流程的先后顺序是很重要的,一定要摸索清楚。日常开发中养成严谨的好习惯,关键时刻才能彰显靠谱。
【EOF】欢迎大家关注我的微信公众号「靠谱的程序员」,让我们一起做靠谱的程序员。
标签:...,先改,商户,状态,付款,程序员,记账,付款单,paymentOrder From: https://www.cnblogs.com/buguge/p/17773278.html