Skip to content

业务讲解-接收支付宝回调通知后如何进行数据更新

概述

在讲解订单支付流程的章节中,介绍了支付的流程:

用户发起订单支付 -> 订单服务执行支付逻辑 -> 调用支付服务 -> 调用支付宝SDK -> 返回支付宝支付页面 -> 用户调用支付宝支付 -> 支付宝处理请求 -> 回调通知

本文就是要讲解支付宝进行回调通知后,回调接口的处理逻辑

支付宝回调通知订单服务接口

流程图

1723691445374-818e9850-ba2b-4c5d-bba9-f676c54e1772.png

控制层

com.damai.controller.OrderController#alipayNotify

java
@ApiOperation(value = "支付宝支付后回调通知")
@PostMapping(value = "/alipay/notify")
public String alipayNotify(@RequestParam Map<String, String> params) {
    return orderService.alipayNotify(params,params.get("out_trade_no"));
}

此接口是让支付宝回调的,所以参数结构要按照支付宝的规定,使用Map结构,并使用**@RequestParam **** **将参数映射到Map上

service层

com.damai.service.OrderService#alipayNotify

java
public String alipayNotify(HttpServletRequest request){
    //将回调中的参数转为Map结构
    Map<String, String> params = new HashMap<>(256);
    if (request instanceof CustomizeRequestWrapper) {
        CustomizeRequestWrapper customizeRequestWrapper = (CustomizeRequestWrapper)request;
        String requestBody = customizeRequestWrapper.getRequestBody();
        params = StringUtil.convertQueryStringToMap(requestBody);
    }
    //获取其中的订单号
    String outTradeNo = params.get("out_trade_no");
    if (StringUtil.isEmpty(outTradeNo)) {
        return "failure";
    }
    //加锁,防止并发问题
    RLock lock = serviceLockTool.getLock(LockType.Reentrant, UPDATE_ORDER_STATUS_LOCK,
            new String[]{outTradeNo});	
    lock.lock();
    try {
        Order order = orderMapper.selectOne(Wrappers.lambdaQuery(Order.class).eq(Order::getOrderNumber, outTradeNo));
        if (Objects.isNull(order)) {
            throw new DaMaiFrameException(BaseCode.ORDER_NOT_EXIST);
        }
        //如果订单已取消则进行退款
        if (Objects.equals(order.getOrderStatus(), OrderStatus.CANCEL.getCode())) {
            RefundDto refundDto = new RefundDto();
            refundDto.setOrderNumber(outTradeNo);
            refundDto.setAmount(order.getOrderPrice());
            refundDto.setChannel("alipay");
            refundDto.setReason("延迟订单关闭");
            ApiResponse<String> response = payClient.refund(refundDto);
            if (response.getCode().equals(BaseCode.SUCCESS.getCode())) {
                //调用支付服务退款成功后,把订单更新为已退款状态
                Order updateOrder = new Order();
                updateOrder.setEditTime(DateUtils.now());
                updateOrder.setOrderStatus(OrderStatus.REFUND.getCode());
                orderMapper.update(updateOrder,Wrappers.lambdaUpdate(Order.class).eq(Order::getOrderNumber, outTradeNo));
            }else {
                log.error("pay服务退款失败 dto : {} response : {}",JSON.toJSONString(refundDto),JSON.toJSONString(response));
            }
            return ALIPAY_NOTIFY_SUCCESS_RESULT;
        }
        
        NotifyDto notifyDto = new NotifyDto();
        notifyDto.setChannel(PayChannel.ALIPAY.getValue());
        notifyDto.setParams(params);
        //调用支付服务,将回调中的参数进行签名验证
        ApiResponse<NotifyVo> notifyResponse = payClient.notify(notifyDto);
        if (!Objects.equals(notifyResponse.getCode(), BaseCode.SUCCESS.getCode())) {
            throw new DaMaiFrameException(notifyResponse);
        }
        //如果验证过程的话则进行订单状态更新
        if (ALIPAY_NOTIFY_SUCCESS_RESULT.equals(notifyResponse.getData().getPayResult())) {
            try {
                orderService.updateOrderRelatedData(Long.parseLong(notifyResponse.getData().getOutTradeNo())
                        ,OrderStatus.PAY);
            }catch (Exception e) {
                log.warn("updateOrderRelatedData warn message",e);
            }
        }
        return notifyResponse.getData().getPayResult();
    }finally {
        lock.unlock();
    }
    
}

首先判断订单状态是否已取消,如果已取消的话,则直接退款。有小伙伴会好奇 这刚支付成功怎么会变成取消状态?

其实存在一种极端场景,用户生成订单后,长时间停留在跳转支付宝的页面,当订单快到了关闭时间后,跳转到支付宝页面,这时支付,支付宝支付成功了,但订单到了延迟关闭时间,已经取消了。

这种场景确实一般人不会这么干,但既然存在,我们就得考虑处理。所以当检查订单已取消的话,则要进行退款。至于 座位的状态和余票数量,在延迟订单关闭时已经做了重置处理了,这里无需考虑

如果订单不是取消状态,说明正常支付,那么继续执行,在处理逻辑中,要调用支付服务,判断支付是否真的成功了

支付服务

java
@ApiOperation(value = "支付后回到通知")
@PostMapping(value = "/notify")
public ApiResponse<NotifyVo> notify(@Valid @RequestBody NotifyDto notifyDto) {
    return ApiResponse.ok(payService.notify(notifyDto));
}
java
@Transactional(rollbackFor = Exception.class)
public NotifyVo notify(NotifyDto notifyDto){
    NotifyVo notifyVo = new NotifyVo();

    log.info("回调通知参数 ===> {}", JSON.toJSONString(notifyDto));
    Map<String, String> params = notifyDto.getParams();
    //验签
    PayStrategyHandler payStrategyHandler = payStrategyContext.get(notifyDto.getChannel());
    boolean signVerifyResult = payStrategyHandler.signVerify(params);
    if (!signVerifyResult) {
        notifyVo.setPayResult(ALIPAY_NOTIFY_FAILURE_RESULT);
        return notifyVo;
    }
    //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验
    //1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
    LambdaQueryWrapper<PayBill> payBillLambdaQueryWrapper =
            Wrappers.lambdaQuery(PayBill.class).eq(PayBill::getOutOrderNo, params.get("out_trade_no"));
    //查询账单
    PayBill payBill = payBillMapper.selectOne(payBillLambdaQueryWrapper);
    //查询账单是否存在
    if (Objects.isNull(payBill)) {
        log.error("账单为空 notifyDto : {}",JSON.toJSONString(notifyDto));
        notifyVo.setPayResult(ALIPAY_NOTIFY_FAILURE_RESULT);
        return notifyVo;
    }
    //如果账单已支付了,直接返回支付宝成功状态
    if (Objects.equals(payBill.getPayBillStatus(), PayBillStatus.PAY.getCode())) {
        log.info("账单已支付 notifyDto : {}",JSON.toJSONString(notifyDto));
        notifyVo.setOutTradeNo(payBill.getOutOrderNo());
        notifyVo.setPayResult(ALIPAY_NOTIFY_SUCCESS_RESULT);
        return notifyVo;
    }
    //如果账单已取消了,直接返回支付宝成功状态
    if (Objects.equals(payBill.getPayBillStatus(), PayBillStatus.CANCEL.getCode())) {
        log.info("账单已取消 notifyDto : {}",JSON.toJSONString(notifyDto));
        notifyVo.setOutTradeNo(payBill.getOutOrderNo());
        notifyVo.setPayResult(ALIPAY_NOTIFY_SUCCESS_RESULT);
        return notifyVo;
    }
    //如果账单已退单了,直接返回支付宝成功状态
    if (Objects.equals(payBill.getPayBillStatus(), PayBillStatus.REFUND.getCode())) {
        log.info("账单已退单 notifyDto : {}",JSON.toJSONString(notifyDto));
        notifyVo.setOutTradeNo(payBill.getOutOrderNo());
        notifyVo.setPayResult(ALIPAY_NOTIFY_SUCCESS_RESULT);
        return notifyVo;
    }
    //验证支付宝回调传入的参数,防止恶意攻击
    boolean dataVerify = payStrategyHandler.dataVerify(notifyDto.getParams(), payBill);
    if (!dataVerify) {
        notifyVo.setPayResult(ALIPAY_NOTIFY_FAILURE_RESULT);
        return notifyVo;
    }
    //更新账单为支付状态
    PayBill updatePayBill = new PayBill();
    updatePayBill.setPayBillStatus(PayBillStatus.PAY.getCode());
    LambdaUpdateWrapper<PayBill> payBillLambdaUpdateWrapper =
            Wrappers.lambdaUpdate(PayBill.class).eq(PayBill::getOutOrderNo, params.get("out_trade_no"));
    payBillMapper.update(updatePayBill,payBillLambdaUpdateWrapper);
    notifyVo.setOutTradeNo(payBill.getOutOrderNo());
    notifyVo.setPayResult(ALIPAY_NOTIFY_SUCCESS_RESULT);
    return notifyVo;
}

此方法是先验证此账单的状态,如果不是为支付状态,直接返回给支付宝成功

如果是未支付状态,则对支付宝回调传入的参数进行验证,如果验证确实没有问题,则将账单修改为支付状态

接着将结果返回给订单服务,订单服务判断调用支付服务的执行结果,如果成功了的话,那么就要把购买的座位状态从锁定修改为已购买

订单服务更新座位状态

com.damai.service.OrderService#updateOrderRelatedData

java
/**
 * 更新订单和购票人订单状态以及操作缓存数据
 * */
@Transactional(rollbackFor = Exception.class)
public void updateOrderRelatedData(Long orderNumber,OrderStatus orderStatus){
    //如果不是取消或者支付操作,则直接抛出异常提示
    if (!(Objects.equals(orderStatus.getCode(), OrderStatus.CANCEL.getCode()) ||
            Objects.equals(orderStatus.getCode(), OrderStatus.PAY.getCode()))) {
        throw new DaMaiFrameException(BaseCode.OPERATE_ORDER_STATUS_NOT_PERMIT);
    }
    //查询订单
    LambdaQueryWrapper<Order> orderLambdaQueryWrapper =
            Wrappers.lambdaQuery(Order.class).eq(Order::getOrderNumber, orderNumber);
    Order order = orderMapper.selectOne(orderLambdaQueryWrapper);
    //检查订单的状态 已取消、已支付、已退单的状态不再执行
    checkOrderStatus(order);
    //将订单更新为取消或者支付状态
    Order updateOrder = new Order();
    updateOrder.setId(order.getId());
    updateOrder.setOrderStatus(orderStatus.getCode());

    //将购票人订单更新为取消或者支付状态
    OrderTicketUser updateOrderTicketUser = new OrderTicketUser();
    updateOrderTicketUser.setOrderStatus(orderStatus.getCode());
    if (Objects.equals(orderStatus.getCode(), OrderStatus.PAY.getCode())) {
        updateOrder.setPayOrderTime(DateUtils.now());
        updateOrderTicketUser.setPayOrderTime(DateUtils.now());
    } else if (Objects.equals(orderStatus.getCode(), OrderStatus.CANCEL.getCode())) {
        updateOrder.setCancelOrderTime(DateUtils.now());
        updateOrderTicketUser.setCancelOrderTime(DateUtils.now());
    }
    //更新订单
    LambdaUpdateWrapper<Order> orderLambdaUpdateWrapper =
            Wrappers.lambdaUpdate(Order.class).eq(Order::getOrderNumber, order.getOrderNumber());
    int updateOrderResult = orderMapper.update(updateOrder,orderLambdaUpdateWrapper);

    //更新购票人订单
    LambdaUpdateWrapper<OrderTicketUser> orderTicketUserLambdaUpdateWrapper =
            Wrappers.lambdaUpdate(OrderTicketUser.class).eq(OrderTicketUser::getOrderNumber, order.getOrderNumber());
    int updateTicketUserOrderResult =
            orderTicketUserMapper.update(updateOrderTicketUser,orderTicketUserLambdaUpdateWrapper);
    if (updateOrderResult <= 0 || updateTicketUserOrderResult <= 0) {
        throw new DaMaiFrameException(BaseCode.ORDER_CANAL_ERROR);
    }
    //查询该订单下的购票人订单列表
    LambdaQueryWrapper<OrderTicketUser> orderTicketUserLambdaQueryWrapper =
            Wrappers.lambdaQuery(OrderTicketUser.class).eq(OrderTicketUser::getOrderNumber, order.getOrderNumber());
    List<OrderTicketUser> orderTicketUserList = orderTicketUserMapper.selectList(orderTicketUserLambdaQueryWrapper);
    if (CollectionUtil.isEmpty(orderTicketUserList)) {
        throw new DaMaiFrameException(BaseCode.TICKET_USER_ORDER_NOT_EXIST);
    }
    Long programId = order.getProgramId();
    Map<Long, List<OrderTicketUser>> orderTicketUserSeatList = 
            orderTicketUserList.stream().collect(Collectors.groupingBy(OrderTicketUser::getTicketCategoryId));
    //查询到购票人的座位
    //seatMap key:票档id value:座位id集合
    Map<Long,List<Long>> seatMap = new HashMap<>(orderTicketUserSeatList.size());
    orderTicketUserSeatList.forEach((k,v) -> {
        seatMap.put(k,v.stream().map(OrderTicketUser::getSeatId).collect(Collectors.toList()));
    });
    //更新缓存相关数据
    updateProgramRelatedDataResolution(programId,seatMap,orderStatus);
}

**updateProgramRelatedDataResolution **方法同样也是负责两种状态的操作,单取消和订单支付,这两种操作刚好也都是相反的

而在讲解订单服务处理延迟消息关闭的时候,分析过订单取消的执行流程,这里再分析一遍订单支付的流程,让小伙伴更加清晰,不用再回去看了

订单支付的流程:

  • 查询主订单 如果为空,抛出异常。如果状态为已取消、已支付、已退单的状态,则不再执行
  • 将主订单修改为已支付状态
  • 将购票人订单修改为已支付状态
  • 查询该订单下的购票人订单列表,过滤出购票人的座位列表
  • 将节目id、座位列表、订单取消类型传入lua执行处理器
  • 组装lua执行需要的数据

将座位状态从锁定修改已售卖

com.damai.service.OrderService#updateProgramRelatedDataResolution

java
public void updateProgramRelatedDataResolution(Long programId,Map<Long,List<Long>> seatMap,OrderStatus orderStatus){
    Map<Long, List<SeatVo>> seatVoMap = new HashMap<>(seatMap.size());
    //从redis中查询锁定中的座位
    seatMap.forEach((k,v) -> {
        seatVoMap.put(k,redisCache.multiGetForHash(
                RedisKeyBuild.createRedisKey(RedisKeyManage.PROGRAM_SEAT_LOCK_RESOLUTION_HASH, programId, k),
                v.stream().map(String::valueOf).collect(Collectors.toList()), SeatVo.class));
   
    });
    if (CollectionUtil.isEmpty(seatVoMap)) {
        throw new DaMaiFrameException(BaseCode.LOCK_SEAT_LIST_EMPTY);
    }
    //票档相关数据
    JSONArray jsonArray = new JSONArray();
    //要添加的座位相关数据
    JSONArray addSeatDatajsonArray = new JSONArray();
    List<TicketCategoryCountDto> ticketCategoryCountDtoList = new ArrayList<>(seatVoMap.size());
    //锁定的座位相关数据
    JSONArray unLockSeatIdjsonArray = new JSONArray();
    //锁定的座位id集合(用于发送给节目服务使用)
    List<Long> unLockSeatIdList = new ArrayList<>();
    seatVoMap.forEach((k,v) -> {
        JSONObject unLockSeatIdjsonObject = new JSONObject();
        //锁定的座位hash的key
        unLockSeatIdjsonObject.put("programSeatLockHashKey", RedisKeyBuild.createRedisKey(
                RedisKeyManage.PROGRAM_SEAT_LOCK_RESOLUTION_HASH, programId, k).getRelKey());
        //扣除锁定的座位数据
        unLockSeatIdjsonObject.put("unLockSeatIdList",v.stream()
                .map(SeatVo::getId).map(String::valueOf).collect(Collectors.toList()));
        unLockSeatIdjsonArray.add(unLockSeatIdjsonObject);
        
        JSONObject seatDatajsonObject = new JSONObject();
        //要添加的座位hash的key
        String seatHashKeyAdd = "";
        //如果是订单取消操作
        if (Objects.equals(orderStatus.getCode(), OrderStatus.CANCEL.getCode())) {
            //要添加的座位hash的key就是未售卖座位
            seatHashKeyAdd = RedisKeyBuild.createRedisKey(
                    RedisKeyManage.PROGRAM_SEAT_NO_SOLD_RESOLUTION_HASH, programId, k).getRelKey();
            for (SeatVo seatVo : v) {
                //座位状态要改成未售卖
                seatVo.setSellStatus(SellStatus.NO_SOLD.getCode());
            }
            //如果是订单支付操作
        }else if (Objects.equals(orderStatus.getCode(), OrderStatus.PAY.getCode())) {
            //要添加的座位hash的key就是已售卖座位
            seatHashKeyAdd = RedisKeyBuild.createRedisKey(
                    RedisKeyManage.PROGRAM_SEAT_SOLD_RESOLUTION_HASH, programId, k).getRelKey();
            for (SeatVo seatVo : v) {
                //座位状态要改成已售卖
                seatVo.setSellStatus(SellStatus.SOLD.getCode());
            }
        }
        seatDatajsonObject.put("seatHashKeyAdd",seatHashKeyAdd);
        List<String> seatDataList = new ArrayList<>();
        for (SeatVo seatVo : v) {
            seatDataList.add(String.valueOf(seatVo.getId()));
            seatDataList.add(JSON.toJSONString(seatVo));
        }
        //如果是订单取消的操作,那么添加到未售卖的座位数据
        //如果是订单支付的操作,那么添加到已售卖的座位数据
        seatDatajsonObject.put("seatDataList",seatDataList);
        addSeatDatajsonArray.add(seatDatajsonObject);
        
        //票档相关数据(只在订单取消操作有用)
        JSONObject jsonObject = new JSONObject();
        //票档的hash的key
        jsonObject.put("programTicketRemainNumberHashKey",RedisKeyBuild.createRedisKey(
                RedisKeyManage.PROGRAM_TICKET_REMAIN_NUMBER_HASH_RESOLUTION, programId, k).getRelKey());
        //票档id
        jsonObject.put("ticketCategoryId",String.valueOf(k));
        //票档恢复的余票数量
        jsonObject.put("count",v.size());
        jsonArray.add(jsonObject);
        
        //组装发送给节目服务的数据
        TicketCategoryCountDto ticketCategoryCountDto = new TicketCategoryCountDto();
        ticketCategoryCountDto.setTicketCategoryId(k);
        ticketCategoryCountDto.setCount((long) v.size());
        ticketCategoryCountDtoList.add(ticketCategoryCountDto);
        
        unLockSeatIdList.addAll(v.stream().map(SeatVo::getId).toList());
    });
    
    List<String> keys = new ArrayList<>();
    //操作类型
    keys.add(String.valueOf(orderStatus.getCode()));
    
    Object[] data = new String[3];
    //扣除锁定的座位数据
    data[0] = JSON.toJSONString(unLockSeatIdjsonArray);
    //如果是订单取消的操作,那么添加到未售卖的座位数据
    //如果是订单支付的操作,那么添加到已售卖的座位数据
    data[1] = JSON.toJSONString(addSeatDatajsonArray);
    //恢复库存数据
    data[2] = JSON.toJSONString(jsonArray);
    //执行lua脚本
    orderProgramCacheResolutionOperate.programCacheReverseOperate(keys,data);
    
    if (Objects.equals(orderStatus.getCode(), OrderStatus.PAY.getCode())) {
        ProgramOperateDataDto programOperateDataDto = new ProgramOperateDataDto();
        programOperateDataDto.setProgramId(programId);
        programOperateDataDto.setSeatIdList(unLockSeatIdList);
        programOperateDataDto.setTicketCategoryCountDtoList(ticketCategoryCountDtoList);
        programOperateDataDto.setSellStatus(SellStatus.SOLD.getCode());
        delayOperateProgramDataSend.sendMessage(JSON.toJSONString(programOperateDataDto));
    }
}

此方法是负责订单取消和订单支付的两种操作,这两种操作正好是彼此相反的,但订单取消多了一个操作就是恢复余票数量,而订单支付已经在生成订单的步骤中将余票扣除了,所以无需再扣除余票数量

订单取消是要恢复余票数量,将座位状态从锁定中修改为未售卖

订单支付是将座位状态从锁定中修改为已售卖

本文讲解的是订单支付流程,所以只分析订单支付操作

下面列举的键是去掉了个人前缀(默认为 damai)的情况下,避免小伙伴会有下面列举的键名和自己启动项目中对不上的情况

keys List结构 是存放操作的类型

  • 要操作的类型
    • 2 订单取消
    • 3 订单支付

keys真实结构json形式展示

json
[
    "3"
]

data 数组结构 是存放要修改的数据

  • 第一个元素 要解除锁定的座位id,是一个数组,数组的元素是String
programSeatLockHashKeyunLockSeatIdList
damai-d_mai_program_seat_lock_resolution_hash_1_22
  • 第二个元素 要添加的座位数据,是一个数组,数组的元素是String的json字符串,存放着座位对象集合、要添加座位的hash的key

座位对象集合:这个数组比较特殊,不是同一个元素,而是一个座位id,一个对应的座位对象,再一个座位id,一个对应的座位对象 ...

seatDataListseatHashKeyAdd
["1","{"colCode":1,"id":1,"price":180,"programId":1,"rowCode":1,"seatType":1,"seatTypeName":"通用座位","sellStatus":3,"ticketCategoryId":2}"
]
damai-d_mai_program_seat_sold_resolution_hash_1_2
  • 第三个元素 票档数量数据,是一个数组,数组的元素是json字符串,存放着票档缓存的key、票档id、要购票的数量 (此元素在订单支付操作中不需要用到,只在订单取消操作中用到
programTicketRemainNumberHashKeyticketCategoryIdcount
damai-d_mai_program_ticket_remain_number_hash_resolution_1_221

data 真实结构json形式展示

json
[
    "[{\"programSeatLockHashKey\":\"damai-d_mai_program_seat_lock_resolution_hash_1_2\",\"unLockSeatIdList\":[\"2\"]}]",
    "[{\"seatDataList\":[\"2\",\"{\\\"colCode\\\":2,\\\"id\\\":2,\\\"price\\\":180,\\\"programId\\\":1,\\\"rowCode\\\":1,\\\"seatType\\\":1,\\\"seatTypeName\\\":\\\"通用座位\\\",\\\"sellStatus\\\":3,\\\"ticketCategoryId\\\":2}\"],\"seatHashKeyAdd\":\"damai-d_mai_program_seat_sold_resolution_hash_1_2\"}]",
    "[{\"programTicketRemainNumberHashKey\":\"damai-d_mai_program_ticket_remain_number_hash_resolution_1_2\",\"ticketCategoryId\":\"2\",\"count\":1}]"
]

介绍一下这些数据在redis中的真正存储:

  • d_mai_program_seat_lock_resolution_hash_节目id_票档id 节目下锁定中的座位集合 值的存储结构为hash,hash的key为座位id,hash的value为座位对象
keyvalue
1
2
  • d_mai_program_seat_sold_resolution_hash_节目id_票档id 节目下已经售卖的座位集合 值的存储结构为hash,hash的key为座位id,hash的value为座位对象
keyvalue
3
4
.......
  • d_mai_program_ticket_remain_number_hash_resolution_节目id_票档id 节目下的票档余票数量  值的存储结构为hash,hash的key为票档id,hash的value为票档数量
keyvalue
10
258

把这些键和数据拼接好后,就是在lua中执行了

lua脚本执行

脚本位置: resources/lua/OrderProgramDataResolution.lua

lua
-- 订单操作的类型 2:订单取消 3:订单支付
local operate_order_status = tonumber(KEYS[1])
-- 解除锁定的座位id列表
local un_lock_seat_id_json_array = cjson.decode(ARGV[1])
-- 座位数据
local add_seat_data_json_array = cjson.decode(ARGV[2])
-- 将锁定的座位集合进行扣除
for index, un_lock_seat_id_json_object in pairs(un_lock_seat_id_json_array) do
    local program_seat_hash_key = un_lock_seat_id_json_object.programSeatLockHashKey
    local un_lock_seat_id_list = un_lock_seat_id_json_object.unLockSeatIdList
    redis.call('HDEL',program_seat_hash_key,unpack(un_lock_seat_id_list))    
end

-- 如果是订单取消的操作,那么添加到未售卖的座位hash数据
-- 如果是订单支付的操作,那么添加到已售卖的座位hash数据
for index, add_seat_data_json_object in pairs(add_seat_data_json_array) do
    local seat_hash_key_add = add_seat_data_json_object.seatHashKeyAdd
    local seat_data_list = add_seat_data_json_object.seatDataList
    redis.call('HMSET',seat_hash_key_add,unpack(seat_data_list))
end

-- 如果是将订单取消
if (operate_order_status == 2) then
    -- 票档数量数据
    local ticket_category_list = cjson.decode(ARGV[3])
    -- 恢复库存
    for index,increase_data in ipairs(ticket_category_list) do
        -- 票档数量的key
        local program_ticket_remain_number_hash_key = increase_data.programTicketRemainNumberHashKey
        local ticket_category_id = increase_data.ticketCategoryId
        local increase_count = increase_data.count
        redis.call('HINCRBY',program_ticket_remain_number_hash_key,ticket_category_id,increase_count)
    end
end

lua中的执行逻辑也是执行订单取消和订单支付两种操作,这里来分析订单支付的流程

**KEYS的数据就是传入的keys,ARGV的数据就是传入的data **

如果是订单取消的流程,那么此时的键具体为

  • operate_order_status 实际为 3
  • program_seat_hash_key 实际为 d_mai_program_seat_lock_resolution_hash_1_2
  • seat_hash_key_add 实际为 d_mai_program_seat_sold_resolution_hash_1_2

执行流程是先从锁定的座位集合中删除掉对应的座位,接着再将对应的座位添加到已经售卖的座位集合中

执行完座位状态修改的操作后,返回支付成功的状态给支付宝

java
//将订单状态更新
if (ALIPAY_NOTIFY_SUCCESS_RESULT.equals(notifyResponse.getData().getPayResult())) {
    orderService.updateOrderRelatedData(Long.parseLong(notifyResponse.getData().getOutTradeNo()),OrderStatus.PAY);
}
return notifyResponse.getData().getPayResult();

以上就是支付宝回调请求后的整个处理流程,但其实是有问题的,比如支付宝回调没有成功执行到接口。支付宝规则说这个调用是异步的,既然是异步那什么时候去执行回调就不一定了,万一时间很长了都没有执行回调呢。这不就造成用户付完钱后订单还是未支付状态吗?这样用户体验肯定会极差

所以光靠支付宝来回调是不够保险,还有加一个兜底的方案,当用户付完款后,跳转到付款成功的页面时,还有主动调用一次状态检查的请求,如果支付宝没有执行回调,那么执行更新的逻辑

关于主动检查的详细讲解,小伙伴可跳转到下面的章节来学习

业务讲解-支付后如何主动检查状态

更新: 2025-09-01 10:02:10
原文: https://www.yuque.com/u22210564/ykdrdh/fg5m1zqgfm3l4r80

Java 后端面试知识库