基于Vue+SpringCloudAlibaba微服务电商项目实战-聚合支付平台-021:支付定时对账与订单30分钟有效期

1 聚合支付中如何实现对账

今日课程任务

  1. 支付宝没有及时的将支付结果通知给商户端,如何解决数据一致性问题
  2. 如何实现解决商户端与支付宝两者间的分布式事务问题
  3. 秒杀成功了,就是不支付,如何实现库存回滚
  4. 基于Redis与MQ实现订单30分钟有效期

做对账:
如果是查询支付宝后台交易金额 > 商户端查询交易额金额,没有关系,可以通过补偿实现。在支付宝这边已经支付,但是商户端支付状态没有更新。
支付宝后台交易金额 < 商户端查询交易额金额,如商户端交易额11万,支付宝后台查询为10万,说明代码有严重bug。

2 用户支付了,异步回调产生接口延迟如何解决

网络抖动造成双方数据不一致性很正常,但是可以采取最终一致性的思想,可以根据商户生成的全局id,主动调用支付宝接口查询状态同步到商户端。

补偿接口:

  1. 根据全局id查询状态是否为已经支付
  2. 如果不是已支付状态的情况下,调用支付宝接口同步状态,支付的渠道由用户提供。3. 判断支付宝回调接口如果返回状态码为10000,人工比对支付时间、下单金额。

自动补偿:循环调用渠道效率非常低,可以根据之前用户点击支付渠道的日志行为记录,后期做自动化补偿。

3 人工补偿实现订单状态最终一致性

人工补偿接口(查询支付宝订单状态补偿更新订单)

public interface PaymentVerifyOrderService {
    /**
     *  人工补偿接口
     * @return
     */
     @GetMapping("/verifyOrder")
     BaseResponse<String> verifyOrder(
             @RequestParam("paymentId") String paymentId,
             @RequestParam("channelId")String channelId);
}
@RestController
public class PaymentVerifyOrderServiceImpl extends BaseApiService implements PaymentVerifyOrderService {
    @Autowired
    private PaymentChannelMapper paymentChannelMapper;

    @Override
    public BaseResponse<String> verifyOrder(String paymentId, String channelId) {
        // 1.验证参数
        if (StringUtils.isEmpty(paymentId)) {
            return setResultError("paymentId支付id不能为空!");
        }
        if (StringUtils.isEmpty(channelId)) {
            return setResultError("channelId支付id不能为空!");
        }
        // 2.使用策略模式
        PaymentChannelEntity pce = paymentChannelMapper.selectBychannelId(channelId);
        if (pce == null) {
            return setResultError("该渠道已关闭或不存在,请联系管理员");
        }
        // 3.根据beanid 从Spring容器中找到策略类执行
        String payBeanId = pce.getPayBeanId();
        if (StringUtils.isEmpty(payBeanId)) {
            return setResultError("没有配置payBeanId");
        }
        PayStrategy payStrategy = SpringContextUtils.getBean(payBeanId, PayStrategy.class);
        // 调用支付宝查询
        return payStrategy.verifyOrder(pce, paymentId);
    }
}
@Component
@Slf4j
public class AliPayStrategy extends BaseApiService implements PayStrategy {

    @Autowired
    private PaymentTransactionMapper paymentTransactionMapper;
    ……
    @Override
    public BaseResponse<String> verifyOrder(PaymentChannelEntity pce, String paymentId) {
        // 先根据支付id,查询是都已经支付过
        PaymentTransactionEntity pte = paymentTransactionMapper.selectByPaymentId(paymentId);
        if (pte == null) {
            return setResultError("根据支付id没有查询到支付信息");
        }
        if (!(PayConstant.PAY_STAY_STATUS.equals(pte.getPaymentStatus()))) {
            return setResultError("不需要实现补偿");
        }
        //  3.如果在商户端没有支付过情况下,则调用第三方支付接口查询 同步订单状态
        //获得初始化的AlipayClient
        AlipayClient alipayClient = new DefaultAlipayClient(pce.getRequestAddress(),
                pce.getMerchantId(), pce.getPrivateKey(), "json",
                AlipayConfig.CHARSET, pce.getPublicKey(), AlipayConfig.SIGN_TYPE);
        //设置请求参数
        AlipayTradeQueryRequest alipayRequest = new AlipayTradeQueryRequest();
        //请二选一设置
        alipayRequest.setBizContent("{\"out_trade_no\":\"" + paymentId + "\"," + "\"trade_no\":\"" + "" + "\"}");
        //请求
        try {
            String result = alipayClient.execute(alipayRequest).getBody();
            JSONObject jsonObject = JSONObject.parseObject(result);
            JSONObject aliPayResponse = jsonObject.getJSONObject("alipay_trade_query_response");
            String payCode = aliPayResponse.getString("code");
            if (!(PayConstant.ALIPAY_PAY_SUCCESS_CODE.equals(payCode))) {
                // 则同步该订单状态
                return setResultError("该支付订单没有在支付宝支付");
            }
            // 修改订单状体
            int updateAliPayResult = paymentTransactionMapper.
                    updatePaymentStatus(PayConstant.PAY_PAID_STATUS + "",
                            paymentId, "ali_pay");
            return updateAliPayResult >= 0 ? setResultSuccess("数据同步为已经支付")
                    : setResultError("系统错误同步失败");
        } catch (Exception e) {
            log.error(">>>verifyOrder()<<", e);
            return setResultError("系统错误同步失败");
        }
    }
}

效果测试:
在这里插入图片描述
不建议异步回调根据支付id查询状态同步
有可能导致异步回调超时产生其他幂等性等问题

4 基于Redis订单超时30分钟超时设计

下单流程:先下订单 -> 库存-1 -> 跳转到支付接口实现支付。如果用户30分钟未支付如何处理?

订单30分钟超时的设计

  1. 基于redis的过期key
    下订单的时候会生成一个令牌,有效期为30分钟,当该key失效的时候会走客户端监听的方法,查询该笔订单是否已经支付,如果没有支付的情况下,则将该订单设置为已经超时,库存同时回滚+1。
    优点:实现简单,监听的客户端也是可以集群的
    缺点:没有重试策略,抗并发性能差,大量的key在同一时间失效会影响到redis客户端监听的性能
    需要注意的问题:业务与支付令牌key使用的redis建议完全分开,否则有可能监听到非支付令牌相关的key过期。
  2. 基于MQ的死信队列
    下订单的时候向MQ服务器端投递一个消息(支付id或者订单id),设置有效期为30分钟,如果在30分钟内没有被消费的情况下,则将该消息存入到死信队列中,死信队列监听的消费者获取该消息查询是否已经支付。
    基于MQ实现性能优于redis实现。

5 Redis过期了,如何获取value值

每次生成token的时候在数据库表中插入一条记录,当key失效的时候根据token查数据库得到支付id,再去查支付状态,如果订单状态为未支付改为已超时同时库存+1。

6 代码整合Redis过期key事件监听

Redis配置
Redis 开启过期key监听
在这里插入图片描述
redis-server.exe ./redis.windows.conf
代码配置

@Configuration
public class RedisListenerConfig {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        System.out.println(message.toString());
    }
}

测试效果:
在这里插入图片描述

7 代码实现人工补偿订单状态

支付令牌日志表

DROP TABLE IF EXISTS `pay_token_log`;
CREATE TABLE `pay_token_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pay_token` varchar(255) DEFAULT NULL,
  `payment_id` varchar(255) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

PayTokenServiceImpl生成token以后加上插入令牌支付日志记录代码

Redis过期监听类

@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    @Autowired
    private PayTokenLogMapper payTokenLogMapper;
    @Autowired
    private PaymentTransactionMapper paymentTransactionMapper;

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String tokenKey = message.toString();
        PayTokenLogEntity payTokenLogEntity = payTokenLogMapper.selectPayTokenLog(tokenKey);
        if (payTokenLogEntity == null) {
            return;
        }
        // 获取支付id
        String paymentId = payTokenLogEntity.getPaymentId();
        // 根据支付id查询支付的详细信息
        PaymentTransactionEntity pte = paymentTransactionMapper.selectByPaymentId(paymentId);
        if (pte == null) {
            return;
        }
        // 如果是为待支付情况下 ,则将该笔订单改为已经超时
        if (!(PayConstant.PAY_STAY_STATUS.equals(pte.getPaymentStatus()))) {
            return;
        }
        // 调用第三方支付接口再次确认下即可,可以根据用户行为判断支付渠道
        int result = paymentTransactionMapper.updatePaymentOvertimeStatus(paymentId);
        if (result > 0) {
            // 调用库存接口增加库存 ---
        }
    }
}

测试效果:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值