package cn.iocoder.yudao.module.pay.service.order;

import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;

import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * {@link PayOrderServiceImpl} 的单元测试类
 *
 * @author 芋艿
 */
@Import({PayOrderServiceImpl.class, PayNoRedisDAO.class})
public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {

    @Resource
    private PayOrderServiceImpl orderService;

    @Resource
    private PayOrderMapper orderMapper;
    @Resource
    private PayOrderExtensionMapper orderExtensionMapper;

    @MockBean
    private PayProperties properties;
    @MockBean
    private PayAppService appService;
    @MockBean
    private PayChannelService channelService;
    @MockBean
    private PayNotifyService notifyService;

    @BeforeEach
    public void setUp() {
        when(properties.getOrderNotifyUrl()).thenReturn("http://127.0.0.1");
    }

    @Test
    public void testGetOrder_id() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class);
        orderMapper.insert(order);
        // 准备参数
        Long id = order.getId();

        // 调用
        PayOrderDO dbOrder = orderService.getOrder(id);
        // 断言
        assertPojoEquals(dbOrder, order);
    }

    @Test
    public void testGetOrder_appIdAndMerchantOrderId() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class);
        orderMapper.insert(order);
        // 准备参数
        Long appId = order.getAppId();
        String merchantOrderId = order.getMerchantOrderId();

        // 调用
        PayOrderDO dbOrder = orderService.getOrder(appId, merchantOrderId);
        // 断言
        assertPojoEquals(dbOrder, order);
    }

    @Test
    public void testGetOrderCountByAppId() {
        // mock 数据（PayOrderDO）
        PayOrderDO order01 = randomPojo(PayOrderDO.class);
        orderMapper.insert(order01);
        PayOrderDO order02 = randomPojo(PayOrderDO.class);
        orderMapper.insert(order02);
        // 准备参数
        Long appId = order01.getAppId();

        // 调用
        Long count = orderService.getOrderCountByAppId(appId);
        // 断言
        assertEquals(count, 1L);
    }

    @Test
    public void testGetOrderPage() {
        // mock 数据
        PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到
            o.setAppId(1L);
            o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
            o.setMerchantOrderId("110");
            o.setChannelOrderNo("220");
            o.setNo("330");
            o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
            o.setCreateTime(buildTime(2018, 1, 15));
        });
        orderMapper.insert(dbOrder);
        // 测试 appId 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L)));
        // 测试 channelCode 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
        // 测试 merchantOrderId 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(randomString())));
        // 测试 channelOrderNo 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelOrderNo(randomString())));
        // 测试 no 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNo(randomString())));
        // 测试 status 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())));
        // 测试 createTime 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(buildTime(2019, 1, 1))));
        // 准备参数
        PayOrderPageReqVO reqVO = new PayOrderPageReqVO();
        reqVO.setAppId(1L);
        reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
        reqVO.setMerchantOrderId("11");
        reqVO.setChannelOrderNo("22");
        reqVO.setNo("33");
        reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
        reqVO.setCreateTime(buildBetweenTime(2018, 1, 10, 2018, 1, 30));

        // 调用
        PageResult<PayOrderDO> pageResult = orderService.getOrderPage(reqVO);
        // 断言
        assertEquals(1, pageResult.getTotal());
        assertEquals(1, pageResult.getList().size());
        assertPojoEquals(dbOrder, pageResult.getList().get(0));
    }

    @Test
    public void testGetOrderList() {
        // mock 数据
        PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到
            o.setAppId(1L);
            o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
            o.setMerchantOrderId("110");
            o.setChannelOrderNo("220");
            o.setNo("330");
            o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
            o.setCreateTime(buildTime(2018, 1, 15));
        });
        orderMapper.insert(dbOrder);
        // 测试 appId 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L)));
        // 测试 channelCode 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
        // 测试 merchantOrderId 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(randomString())));
        // 测试 channelOrderNo 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelOrderNo(randomString())));
        // 测试 no 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNo(randomString())));
        // 测试 status 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())));
        // 测试 createTime 不匹配
        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(buildTime(2019, 1, 1))));
        // 准备参数
        PayOrderExportReqVO reqVO = new PayOrderExportReqVO();
        reqVO.setAppId(1L);
        reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
        reqVO.setMerchantOrderId("11");
        reqVO.setChannelOrderNo("22");
        reqVO.setNo("33");
        reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
        reqVO.setCreateTime(buildBetweenTime(2018, 1, 10, 2018, 1, 30));

        // 调用
        List<PayOrderDO> list = orderService.getOrderList(reqVO);
        // 断言
        assertEquals(1, list.size());
        assertPojoEquals(dbOrder, list.get(0));
    }

    @Test
    public void testCreateOrder_success() {
        // mock 参数
        PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class,
                o -> o.setAppKey("demo").setMerchantOrderId("10")
                        .setSubject(randomString()).setBody(randomString()));
        // mock 方法
        PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1"));
        when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app);

        // 调用
        Long orderId = orderService.createOrder(reqDTO);
        // 断言
        PayOrderDO order = orderMapper.selectById(orderId);
        assertPojoEquals(order, reqDTO);
        assertEquals(order.getAppId(), 1L);
        assertEquals(order.getNotifyUrl(), "http://127.0.0.1");
        assertEquals(order.getStatus(), PayOrderStatusEnum.WAITING.getStatus());
        assertEquals(order.getRefundPrice(), 0);
    }

    @Test
    public void testCreateOrder_exists() {
        // mock 参数
        PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class,
                o -> o.setAppKey("demo").setMerchantOrderId("10"));
        // mock 数据
        PayOrderDO dbOrder = randomPojo(PayOrderDO.class,  o -> o.setAppId(1L).setMerchantOrderId("10"));
        orderMapper.insert(dbOrder);
        // mock 方法
        PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1"));
        when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app);

        // 调用
        Long orderId = orderService.createOrder(reqDTO);
        // 断言
        PayOrderDO order = orderMapper.selectById(orderId);
        assertPojoEquals(dbOrder, order);
    }

    @Test
    public void testSubmitOrder_notFound() {
        // 准备参数
        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class);
        String userIp = randomString();

        // 调用, 并断言异常
        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_NOT_FOUND);
    }

    @Test
    public void testSubmitOrder_notWaiting() {
        // mock 数据（order）
        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus()));
        orderMapper.insert(order);
        // 准备参数
        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()));
        String userIp = randomString();

        // 调用, 并断言异常
        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_STATUS_IS_NOT_WAITING);
    }

    @Test
    public void testSubmitOrder_isSuccess() {
        // mock 数据（order）
        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
        orderMapper.insert(order);
        // 准备参数
        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()));
        String userIp = randomString();

        // 调用, 并断言异常
        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_STATUS_IS_SUCCESS);
    }

    @Test
    public void testSubmitOrder_expired() {
        // mock 数据（order）
        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                .setExpireTime(addTime(Duration.ofDays(-1))));
        orderMapper.insert(order);
        // 准备参数
        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()));
        String userIp = randomString();

        // 调用, 并断言异常
        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_IS_EXPIRED);
    }

    @Test
    public void testSubmitOrder_channelNotFound() {
        // mock 数据（order）
        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
        orderMapper.insert(order);
        // 准备参数
        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
                .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
        String userIp = randomString();
        // mock 方法（app）
        PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
        when(appService.validPayApp(eq(1L))).thenReturn(app);
        // mock 方法（channel）
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
        when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
                .thenReturn(channel);

        // 调用, 并断言异常
        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), CHANNEL_NOT_FOUND);
    }

    @Test // 调用 unifiedOrder 接口，返回存在渠道错误
    public void testSubmitOrder_channelError() {
        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
                    .thenReturn(payOrderServiceImpl);

            // mock 数据（order）
            PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                    .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
            orderMapper.insert(order);
            // 准备参数
            PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
                    .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
            String userIp = randomString();
            // mock 方法（app）
            PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
            when(appService.validPayApp(eq(1L))).thenReturn(app);
            // mock 方法（channel）
            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
                    .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
            when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
                    .thenReturn(channel);
            // mock 方法（client）
            PayClient client = mock(PayClient.class);
            when(channelService.getPayClient(eq(10L))).thenReturn(client);
            // mock 方法（）
            PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o ->
                    o.setChannelErrorCode("001").setChannelErrorMsg("模拟异常"));
            when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
                assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo());
                assertThat(payOrderUnifiedReqDTO)
                        .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime")
                        .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10",
                                reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime());
                return true;
            }))).thenReturn(unifiedOrderResp);

            // 调用，并断言异常
            assertServiceException(() -> orderService.submitOrder(reqVO, userIp),
                    PAY_ORDER_SUBMIT_CHANNEL_ERROR, "001", "模拟异常");
            // 断言，数据记录（PayOrderExtensionDO）
            PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
            assertNotNull(orderExtension);
            assertThat(orderExtension).extracting("no", "orderId").isNotNull();
            assertThat(orderExtension)
                    .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
                            "channelErrorCode", "channelErrorMsg", "channelNotifyData")
                    .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
                            PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
                            null, null, null);
        }
    }

    @Test
    public void testSubmitOrder_success() {
        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
                    .thenReturn(payOrderServiceImpl);

            // mock 数据（order）
            PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                    .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
            orderMapper.insert(order);
            // 准备参数
            PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
                    .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
            String userIp = randomString();
            // mock 方法（app）
            PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
            when(appService.validPayApp(eq(1L))).thenReturn(app);
            // mock 方法（channel）
            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
                    .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
            when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
                    .thenReturn(channel);
            // mock 方法（client）
            PayClient client = mock(PayClient.class);
            when(channelService.getPayClient(eq(10L))).thenReturn(client);
            // mock 方法（支付渠道的调用）
            PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null)
                    .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou"));
            when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
                assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo());
                assertThat(payOrderUnifiedReqDTO)
                        .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime")
                        .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10",
                                reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime());
                return true;
            }))).thenReturn(unifiedOrderResp);

            // 调用
            PayOrderSubmitRespVO result = orderService.submitOrder(reqVO, userIp);
            // 断言，数据记录（PayOrderExtensionDO）
            PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
            assertNotNull(orderExtension);
            assertThat(orderExtension).extracting("no", "orderId").isNotNull();
            assertThat(orderExtension)
                    .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
                            "channelErrorCode", "channelErrorMsg", "channelNotifyData")
                    .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
                            PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
                            null, null, null);
            // 断言，返回（PayOrderSubmitRespVO）
            assertThat(result)
                    .extracting("status", "displayMode", "displayContent")
                    .containsExactly(PayOrderStatusEnum.WAITING.getStatus(), PayOrderDisplayModeEnum.URL.getMode(), "tudou");
            // 断言，调用
            verify(payOrderServiceImpl).notifyOrder(same(channel), same(unifiedOrderResp));
        }
    }

    @Test
    public void testValidateOrderActuallyPaid_dbPaid() {
        // 准备参数
        Long id = randomLongId();
        // mock 方法（OrderExtension 已支付）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
        orderExtensionMapper.insert(orderExtension);

        // 调用，并断言异常
        assertServiceException(() -> orderService.validateOrderActuallyPaid(id),
                PAY_ORDER_EXTENSION_IS_PAID);
    }

    @Test
    public void testValidateOrderActuallyPaid_remotePaid() {
        // 准备参数
        Long id = randomLongId();
        // mock 方法（OrderExtension 已支付）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.WAITING.getStatus()));
        orderExtensionMapper.insert(orderExtension);
        // mock 方法（PayClient 已支付）
        PayClient client = mock(PayClient.class);
        when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client);
        when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())));

        // 调用，并断言异常
        assertServiceException(() -> orderService.validateOrderActuallyPaid(id),
                PAY_ORDER_EXTENSION_IS_PAID);
    }

    @Test
    public void testValidateOrderActuallyPaid_success() {
        // 准备参数
        Long id = randomLongId();
        // mock 方法（OrderExtension 已支付）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.WAITING.getStatus()));
        orderExtensionMapper.insert(orderExtension);
        // mock 方法（PayClient 已支付）
        PayClient client = mock(PayClient.class);
        when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client);
        when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())));

        // 调用，并断言异常
        orderService.validateOrderActuallyPaid(id);
    }

    @Test
    public void testNotifyOrder_channelId() {
        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
                    .thenReturn(payOrderServiceImpl);
            // 准备参数
            Long channelId = 10L;
            PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class);
            // mock 方法（channel）
            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
            when(channelService.validPayChannel(eq(10L))).thenReturn(channel);

            // 调用
            orderService.notifyOrder(channelId, notify);
            // 断言
            verify(payOrderServiceImpl).notifyOrder(same(channel), same(notify));
        }
    }

    @Test
    public void testNotifyOrderSuccess_orderExtension_notFound() {
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()));

        // 调用，并断言异常
        assertServiceException(() -> orderService.notifyOrder(channel, notify),
                PAY_ORDER_EXTENSION_NOT_FOUND);
    }

    @Test
    public void testNotifyOrderSuccess_orderExtension_closed() {
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
                        .setNo("P110"));
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
                        .setOutTradeNo("P110"));

        // 调用，并断言异常
        assertServiceException(() -> orderService.notifyOrder(channel, notify),
                PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
    }

    @Test
    public void testNotifyOrderSuccess_order_notFound() {
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
                        .setNo("P110"));
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
                        .setOutTradeNo("P110"));

        // 调用，并断言异常
        assertServiceException(() -> orderService.notifyOrder(channel, notify),
                PAY_ORDER_NOT_FOUND);
        // 断言 PayOrderExtensionDO ：数据更新被回滚
        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
    }

    @Test
    public void testNotifyOrderSuccess_order_closed() {
        testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.CLOSED.getStatus());
    }

    @Test
    public void testNotifyOrderSuccess_order_refund() {
        testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.REFUND.getStatus());
    }

    private void testNotifyOrderSuccess_order_closedOrRefund(Integer status) {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status));
        orderMapper.insert(order);
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
                        .setNo("P110")
                        .setOrderId(order.getId()));
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
                        .setOutTradeNo("P110"));

        // 调用，并断言异常
        assertServiceException(() -> orderService.notifyOrder(channel, notify),
                PAY_ORDER_STATUS_IS_NOT_WAITING);
        // 断言 PayOrderExtensionDO ：数据未更新，因为它是 SUCCESS
        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
    }

    @Test
    public void testNotifyOrderSuccess_order_paid() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
        orderMapper.insert(order);
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
                        .setNo("P110")
                        .setOrderId(order.getId()));
        orderExtensionMapper.insert(orderExtension);
        // 重要：需要将 order 的 extensionId 更新下
        order.setExtensionId(orderExtension.getId());
        orderMapper.updateById(order);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
                        .setOutTradeNo("P110"));

        // 调用，并断言异常
        orderService.notifyOrder(channel, notify);
        // 断言 PayOrderExtensionDO ：数据未更新，因为它是 SUCCESS
        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
        // 断言 PayOrderDO ：数据未更新，因为它是 SUCCESS
        assertPojoEquals(order, orderMapper.selectOne(null));
        // 断言，调用
        verify(notifyService, never()).createPayNotifyTask(anyInt(), anyLong());
    }

    @Test
    public void testNotifyOrderSuccess_order_waiting() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setPrice(10));
        orderMapper.insert(order);
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setNo("P110")
                        .setOrderId(order.getId()));
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
                .setFeeRate(10D));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
                        .setOutTradeNo("P110"));

        // 调用，并断言异常
        orderService.notifyOrder(channel, notify);
        // 断言 PayOrderExtensionDO ：数据未更新，因为它是 SUCCESS
        orderExtension.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
                .setChannelNotifyData(toJsonString(notify));
        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null),
                "updateTime", "updater");
        // 断言 PayOrderDO ：数据未更新，因为它是 SUCCESS
        order.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
                .setChannelId(10L).setChannelCode(channel.getCode())
                .setSuccessTime(notify.getSuccessTime()).setExtensionId(orderExtension.getId()).setNo(orderExtension.getNo())
                .setChannelOrderNo(notify.getChannelOrderNo()).setChannelUserId(notify.getChannelUserId())
                .setChannelFeeRate(10D).setChannelFeePrice(1);
        assertPojoEquals(order, orderMapper.selectOne(null),
                "updateTime", "updater");
        // 断言，调用
        verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.ORDER.getType()),
            eq(orderExtension.getOrderId()));
    }

    @Test
    public void testNotifyOrderClosed_orderExtension_notFound() {
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus()));

        // 调用，并断言异常
        assertServiceException(() -> orderService.notifyOrder(channel, notify),
                PAY_ORDER_EXTENSION_NOT_FOUND);
    }

    @Test
    public void testNotifyOrderClosed_orderExtension_closed() {
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
                        .setNo("P110"));
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())
                        .setOutTradeNo("P110"));

        // 调用，并断言
        orderService.notifyOrder(channel, notify);
        // 断言 PayOrderExtensionDO ：数据未更新，因为它是 CLOSED
        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
    }

    @Test
    public void testNotifyOrderClosed_orderExtension_paid() {
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
                        .setNo("P110"));
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())
                        .setOutTradeNo("P110"));

        // 调用，并断言
        orderService.notifyOrder(channel, notify);
        // 断言 PayOrderExtensionDO ：数据未更新，因为它是 SUCCESS
        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
    }

    @Test
    public void testNotifyOrderClosed_orderExtension_refund() {
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
                        .setNo("P110"));
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())
                        .setOutTradeNo("P110"));

        // 调用，并断言异常
        assertServiceException(() -> orderService.notifyOrder(channel, notify),
                PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
    }

    @Test
    public void testNotifyOrderClosed_orderExtension_waiting() {
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setNo("P110"));
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())
                        .setOutTradeNo("P110"));

        // 调用
        orderService.notifyOrder(channel, notify);
        // 断言 PayOrderExtensionDO
        orderExtension.setStatus(PayOrderStatusEnum.CLOSED.getStatus()).setChannelNotifyData(toJsonString(notify))
                .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg());
        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null),
                "updateTime", "updater");
    }

    @Test
    public void testUpdateOrderRefundPrice_notFound() {
        // 准备参数
        Long id = randomLongId();
        Integer incrRefundPrice = randomInteger();

        // 调用，并断言异常
        assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice),
                PAY_ORDER_NOT_FOUND);
    }

    @Test
    public void testUpdateOrderRefundPrice_waiting() {
        testUpdateOrderRefundPrice_waitingOrClosed(PayOrderStatusEnum.WAITING.getStatus());
    }

    @Test
    public void testUpdateOrderRefundPrice_closed() {
        testUpdateOrderRefundPrice_waitingOrClosed(PayOrderStatusEnum.CLOSED.getStatus());
    }

    private void testUpdateOrderRefundPrice_waitingOrClosed(Integer status) {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(status));
        orderMapper.insert(order);
        // 准备参数
        Long id = order.getId();
        Integer incrRefundPrice = randomInteger();

        // 调用，并断言异常
        assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice),
                PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
    }

    @Test
    public void testUpdateOrderRefundPrice_priceExceed() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
                        .setRefundPrice(1).setPrice(10));
        orderMapper.insert(order);
        // 准备参数
        Long id = order.getId();
        Integer incrRefundPrice = 10;

        // 调用，并断言异常
        assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice),
                REFUND_PRICE_EXCEED);
    }

    @Test
    public void testUpdateOrderRefundPrice_refund() {
        testUpdateOrderRefundPrice_refundOrSuccess(PayOrderStatusEnum.REFUND.getStatus());
    }

    @Test
    public void testUpdateOrderRefundPrice_success() {
        testUpdateOrderRefundPrice_refundOrSuccess(PayOrderStatusEnum.SUCCESS.getStatus());
    }

    private void testUpdateOrderRefundPrice_refundOrSuccess(Integer status) {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(status).setRefundPrice(1).setPrice(10));
        orderMapper.insert(order);
        // 准备参数
        Long id = order.getId();
        Integer incrRefundPrice = 8;

        // 调用
        orderService.updateOrderRefundPrice(id, incrRefundPrice);
        // 断言
        order.setRefundPrice(9).setStatus(PayOrderStatusEnum.REFUND.getStatus());
        assertPojoEquals(order, orderMapper.selectOne(null),
                "updateTime", "updater");
    }

    @Test
    public void testGetOrderExtension() {
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class);
        orderExtensionMapper.insert(orderExtension);
        // 准备参数
        Long id = orderExtension.getId();

        // 调用
        PayOrderExtensionDO dbOrderExtension = orderService.getOrderExtension(id);
        // 断言
        assertPojoEquals(dbOrderExtension, orderExtension);
    }

    @Test
    public void testSyncOrder_payClientNotFound() {
        // 准备参数
        LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10));
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setCreateTime(LocalDateTime.now()));
        orderExtensionMapper.insert(orderExtension);

        // 调用
        int count = orderService.syncOrder(minCreateTime);
        // 断言
        assertEquals(count, 0);
    }

    @Test
    public void testSyncOrder_exception() {
        // 准备参数
        LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10));
        // mock 数据（PayOrderExtensionDO）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setChannelId(10L)
                        .setCreateTime(LocalDateTime.now()));
        orderExtensionMapper.insert(orderExtension);
        // mock 方法（PayClient）
        PayClient client = mock(PayClient.class);
        when(channelService.getPayClient(eq(10L))).thenReturn(client);
        // mock 方法（PayClient 异常）
        when(client.getOrder(any())).thenThrow(new RuntimeException());

        // 调用
        int count = orderService.syncOrder(minCreateTime);
        // 断言
        assertEquals(count, 0);
    }

    @Test
    public void testSyncOrder_orderSuccess() {
        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
                    .thenReturn(payOrderServiceImpl);

            // 准备参数
            LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10));
            // mock 数据（PayOrderExtensionDO）
            PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                    o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                            .setChannelId(10L).setNo("P110")
                            .setCreateTime(LocalDateTime.now()));
            orderExtensionMapper.insert(orderExtension);
            // mock 方法（PayClient）
            PayClient client = mock(PayClient.class);
            when(channelService.getPayClient(eq(10L))).thenReturn(client);
            // mock 方法（PayClient 成功返回）
            PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
                    o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
            when(client.getOrder(eq("P110"))).thenReturn(respDTO);
            // mock 方法（PayChannelDO）
            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
            when(channelService.validPayChannel(eq(10L))).thenReturn(channel);

            // 调用
            int count = orderService.syncOrder(minCreateTime);
            // 断言
            assertEquals(count, 1);
            verify(payOrderServiceImpl).notifyOrder(same(channel), same(respDTO));
        }
    }

    @Test
    public void testSyncOrder_orderClosed() {
        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
                    .thenReturn(payOrderServiceImpl);

            // 准备参数
            LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10));
            // mock 数据（PayOrderExtensionDO）
            PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                    o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                            .setChannelId(10L).setNo("P110")
                            .setCreateTime(LocalDateTime.now()));
            orderExtensionMapper.insert(orderExtension);
            // mock 方法（PayClient）
            PayClient client = mock(PayClient.class);
            when(channelService.getPayClient(eq(10L))).thenReturn(client);
            // mock 方法（PayClient 成功返回）
            PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
                    o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()));
            when(client.getOrder(eq("P110"))).thenReturn(respDTO);
            // mock 方法（PayChannelDO）
            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
            when(channelService.validPayChannel(eq(10L))).thenReturn(channel);

            // 调用
            int count = orderService.syncOrder(minCreateTime);
            // 断言
            assertEquals(count, 0);
            verify(payOrderServiceImpl, never()).notifyOrder(same(channel), same(respDTO));
        }
    }

    @Test
    public void testExpireOrder_orderExtension_isSuccess() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setExpireTime(addTime(Duration.ofMinutes(-1))));
        orderMapper.insert(order);
        // mock 数据（PayOrderExtensionDO 已支付）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
                        .setOrderId(order.getId()));
        orderExtensionMapper.insert(orderExtension);
        // mock 方法（PayClient）
        PayClient client = mock(PayClient.class);
        when(channelService.getPayClient(eq(10L))).thenReturn(client);

        // 调用
        int count = orderService.expireOrder();
        // 断言
        assertEquals(count, 0);
        // 断言 order 没有变化，因为没更新
        assertPojoEquals(order, orderMapper.selectOne(null));
    }

    @Test
    public void testExpireOrder_payClient_notFound() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setExpireTime(addTime(Duration.ofMinutes(-1))));
        orderMapper.insert(order);
        // mock 数据（PayOrderExtensionDO 等待中）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setOrderId(order.getId())
                        .setChannelId(10L));
        orderExtensionMapper.insert(orderExtension);

        // 调用
        int count = orderService.expireOrder();
        // 断言
        assertEquals(count, 0);
        // 断言 order 没有变化，因为没更新
        assertPojoEquals(order, orderMapper.selectOne(null));
    }

    @Test
    public void testExpireOrder_getOrder_isRefund() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setExpireTime(addTime(Duration.ofMinutes(-1))));
        orderMapper.insert(order);
        // mock 数据（PayOrderExtensionDO 等待中）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setOrderId(order.getId()).setNo("P110")
                        .setChannelId(10L));
        orderExtensionMapper.insert(orderExtension);
        // mock 方法（PayClient）
        PayClient client = mock(PayClient.class);
        when(channelService.getPayClient(eq(10L))).thenReturn(client);
        // mock 方法（PayClient 退款返回）
        PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus()));
        when(client.getOrder(eq("P110"))).thenReturn(respDTO);

        // 调用
        int count = orderService.expireOrder();
        // 断言
        assertEquals(count, 0);
        // 断言 order 没有变化，因为没更新
        assertPojoEquals(order, orderMapper.selectOne(null));
    }

    @Test
    public void testExpireOrder_getOrder_isSuccess() {
        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
                    .thenReturn(payOrderServiceImpl);

            // mock 数据（PayOrderDO）
            PayOrderDO order = randomPojo(PayOrderDO.class,
                    o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                            .setExpireTime(addTime(Duration.ofMinutes(-1))));
            orderMapper.insert(order);
            // mock 数据（PayOrderExtensionDO 等待中）
            PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                    o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                            .setOrderId(order.getId()).setNo("P110")
                            .setChannelId(10L));
            orderExtensionMapper.insert(orderExtension);
            // mock 方法（PayClient）
            PayClient client = mock(PayClient.class);
            when(channelService.getPayClient(eq(10L))).thenReturn(client);
            // mock 方法（PayClient 成功返回）
            PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
                    o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
            when(client.getOrder(eq("P110"))).thenReturn(respDTO);
            // mock 方法（PayChannelDO）
            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
            when(channelService.validPayChannel(eq(10L))).thenReturn(channel);

            // 调用
            int count = orderService.expireOrder();
            // 断言
            assertEquals(count, 0);
            // 断言 order 没有变化，因为没更新
            assertPojoEquals(order, orderMapper.selectOne(null));
            verify(payOrderServiceImpl).notifyOrder(same(channel), same(respDTO));
        }
    }

    @Test
    public void testExpireOrder_success() {
        // mock 数据（PayOrderDO）
        PayOrderDO order = randomPojo(PayOrderDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setExpireTime(addTime(Duration.ofMinutes(-1))));
        orderMapper.insert(order);
        // mock 数据（PayOrderExtensionDO 等待中）
        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
                        .setOrderId(order.getId()).setNo("P110")
                        .setChannelId(10L));
        orderExtensionMapper.insert(orderExtension);
        // mock 方法（PayClient）
        PayClient client = mock(PayClient.class);
        when(channelService.getPayClient(eq(10L))).thenReturn(client);
        // mock 方法（PayClient 关闭返回）
        PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class,
                o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()));
        when(client.getOrder(eq("P110"))).thenReturn(respDTO);

        // 调用
        int count = orderService.expireOrder();
        // 断言
        assertEquals(count, 1);
        // 断言 extension 变化
        orderExtension.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
                .setChannelNotifyData(toJsonString(respDTO));
        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null),
                "updateTime", "updater");
        // 断言 order 变化
        order.setStatus(PayOrderStatusEnum.CLOSED.getStatus());
        assertPojoEquals(order, orderMapper.selectOne(null),
                "updateTime", "updater");
    }

}
