package com.talentcard.server.business.payment; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import java.util.Map; import com.talentcard.server.business.PageResult; import com.talentcard.server.business.payment.huifu.HuifuRefundGateway; import com.talentcard.server.business.portal.PendingUsePortalService; import com.talentcard.server.business.portal.UsageRecordPortalService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; /** * Seam: how the talent app reads an order that was cancelled before it was ever paid. * *

「已取消」covers two different stories and they must not be merged: an order the talent * abandoned while it was still unpaid(订单取消,无需退款)and an order whose money was given back * (退款处理,退款中 / 退款成功 / 退款失败). The first one never involved money, so it asks the * provider nothing and reads as a plain cancellation; the refund dimension only adds a reason to the * same state, it does not take the state over. */ @SpringBootTest @ActiveProfiles("test") class CancelledOrderStateTests { @Autowired private JdbcTemplate jdbc; @Autowired private UsageRecordPortalService usageRecordPortalService; @Autowired private PendingUsePortalService pendingUsePortalService; @MockitoBean private HuifuRefundGateway refundGateway; private Long talentId; private String suffix; @BeforeEach void prepareData() { suffix = String.valueOf(System.nanoTime()); jdbc.update("insert into talent_user(openid, name, mobile, status, deleted) values (?, ?, ?, 1, 0)", "cancelled-order-openid-" + suffix, "Cancelled order user", "13900000005"); talentId = jdbc.queryForObject("select max(id) from talent_user", Long.class); } @Test void anOrderCancelledBeforePaymentIsStillListedAsCancelled() { String orderNo = cancelledUnpaidOrder(); assertThat(recordOf(orderNo)).containsEntry("status", "cancelled"); } @Test void anOrderCancelledBeforePaymentReadsAsACancellationThatNeedsNoRefund() { String orderNo = cancelledUnpaidOrder(); Map detail = pendingUsePortalService.cancelledDetail(orderNo, talentId); assertThat(detail) .containsEntry("cancelSource", "订单取消") .containsEntry("refundStatusText", "无需退款"); assertThat(((Number) detail.get("actualPaidAmount")).longValue()) .as("没付过钱就不该显示退过钱") .isZero(); verify(refundGateway, never()).queryRefund(any(), any()); } /** An order the talent cancelled while it was still unpaid: no payment, no refund. */ private String cancelledUnpaidOrder() { String orderNo = "CANCELLED-UNPAID-" + suffix; jdbc.update(""" insert into benefit_order(order_no, talent_id, order_type, order_amount, base_pay_amount, pay_amount, order_status, verify_status, product_name_snapshot) values (?, ?, 'GROUP_BUY', 2000, 2000, 2000, 2, 0, '取消测试订单') """, orderNo, talentId); return orderNo; } private Map recordOf(String orderNo) { PageResult> page = usageRecordPortalService.list(talentId, null, 1, 50); return page.records().stream() .filter(record -> orderNo.equals(String.valueOf(record.get("businessId")))) .findFirst() .orElseThrow(() -> new AssertionError("Order " + orderNo + " is missing from the talent order list")); } }