|
@@ -9,6 +9,8 @@ import com.payment.platform.module.payment.account.mapper.DepositMapper;
|
|
|
import com.payment.platform.module.payment.account.mapper.TransferMapper;
|
|
import com.payment.platform.module.payment.account.mapper.TransferMapper;
|
|
|
import com.payment.platform.module.payment.account.mapper.WithdrawMapper;
|
|
import com.payment.platform.module.payment.account.mapper.WithdrawMapper;
|
|
|
import com.payment.platform.module.payment.facetoface.mapper.F2fTradeRecordMapper;
|
|
import com.payment.platform.module.payment.facetoface.mapper.F2fTradeRecordMapper;
|
|
|
|
|
+import com.payment.platform.module.payment.har.enums.HarOrderStatus;
|
|
|
|
|
+import com.payment.platform.module.payment.har.mapper.HarOrderMapper;
|
|
|
import com.payment.platform.module.payment.leshouying.enums.LsyOrderStatus;
|
|
import com.payment.platform.module.payment.leshouying.enums.LsyOrderStatus;
|
|
|
import com.payment.platform.module.payment.leshouying.mapper.LsyOrderMapper;
|
|
import com.payment.platform.module.payment.leshouying.mapper.LsyOrderMapper;
|
|
|
import com.payment.platform.module.payment.notification.mapper.PayBillMapper;
|
|
import com.payment.platform.module.payment.notification.mapper.PayBillMapper;
|
|
@@ -36,8 +38,9 @@ import static org.mockito.Mockito.verifyNoInteractions;
|
|
|
import static org.mockito.Mockito.when;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 首页「银联收款统计」这张卡的数从哪来 —— 乐收赢聚合腿(用户 2026-10-05 裁定:不再统计当面付;
|
|
|
|
|
- * 同日定名 lsy = 银联收款、har = 支付宝收款)。
|
|
|
|
|
|
|
+ * 首页统计四块卡片的数从哪来 —— 乐收赢聚合腿(「银联收款统计」)与汇安融聚合腿
|
|
|
|
|
+ * (「支付宝收款」,用户 2026-10-05 裁定:不再统计当面付;同日定名 lsy = 银联收款、
|
|
|
|
|
+ * har = 支付宝收款)。
|
|
|
*
|
|
*
|
|
|
* <p>钉三件事:
|
|
* <p>钉三件事:
|
|
|
* <ol>
|
|
* <ol>
|
|
@@ -71,6 +74,7 @@ class AccountServiceStatTest {
|
|
|
@Mock private PayBillMapper payBillMapper;
|
|
@Mock private PayBillMapper payBillMapper;
|
|
|
@Mock private F2fTradeRecordMapper f2fTradeRecordMapper;
|
|
@Mock private F2fTradeRecordMapper f2fTradeRecordMapper;
|
|
|
@Mock private LsyOrderMapper lsyOrderMapper;
|
|
@Mock private LsyOrderMapper lsyOrderMapper;
|
|
|
|
|
+ @Mock private HarOrderMapper harOrderMapper;
|
|
|
@Mock private AlipayTransferService alipayTransferService;
|
|
@Mock private AlipayTransferService alipayTransferService;
|
|
|
@Mock private ReceiptFileFetcher receiptFileFetcher;
|
|
@Mock private ReceiptFileFetcher receiptFileFetcher;
|
|
|
|
|
|
|
@@ -80,7 +84,7 @@ class AccountServiceStatTest {
|
|
|
void setUp() {
|
|
void setUp() {
|
|
|
service = new AccountService(accountMapper, transferMapper, depositMapper, withdrawMapper,
|
|
service = new AccountService(accountMapper, transferMapper, depositMapper, withdrawMapper,
|
|
|
alipayClientFactory, redisTemplate, payBillMapper, f2fTradeRecordMapper,
|
|
alipayClientFactory, redisTemplate, payBillMapper, f2fTradeRecordMapper,
|
|
|
- lsyOrderMapper, alipayTransferService, receiptFileFetcher);
|
|
|
|
|
|
|
+ lsyOrderMapper, harOrderMapper, alipayTransferService, receiptFileFetcher);
|
|
|
// 初始化 MyBatis-Plus lambda 元数据缓存,使 LambdaQueryWrapper 可在无 Spring 上下文的单测里构造
|
|
// 初始化 MyBatis-Plus lambda 元数据缓存,使 LambdaQueryWrapper 可在无 Spring 上下文的单测里构造
|
|
|
MybatisConfiguration configuration = new MybatisConfiguration();
|
|
MybatisConfiguration configuration = new MybatisConfiguration();
|
|
|
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), TransferEntity.class);
|
|
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), TransferEntity.class);
|
|
@@ -171,16 +175,116 @@ class AccountServiceStatTest {
|
|
|
assertEquals(today.getValue().minusDays(7), weekAgo.getValue());
|
|
assertEquals(today.getValue().minusDays(7), weekAgo.getValue());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ==================== 支付宝收款(汇安融)腿 ====================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * ★ 汇安融的钱**本来就是元**({@code decimal(16,2)}),这条腿**不许**再除 100 ——
|
|
|
|
|
+ * 与乐收赢那条腿唯一的差别就在这里,而两条腿长得几乎一样(复制粘贴的天然候选),
|
|
|
|
|
+ * 所以必须有一条用例按「除 100 会怎样」挑数字:
|
|
|
|
|
+ * 100 元若被除 100 会变成 1.00,而这里断言的就是 100.00。
|
|
|
|
|
+ * 反过来乐收赢那条腿断言的是「不换算会大 100 倍」—— 一正一反,谁也替不了谁。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void 支付宝收款统计_元不再除100() {
|
|
|
|
|
+ when(harOrderMapper.statCollectedAmount(any(), any(), any(), any()))
|
|
|
|
|
+ .thenReturn(Map.of(
|
|
|
|
|
+ "amount_of_today", new BigDecimal("123.45"),
|
|
|
|
|
+ "amount_of_7days", new BigDecimal("100"),
|
|
|
|
|
+ "amount_of_all", new BigDecimal("0")));
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> r = service.statHarCollectedAmount(null);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals("123.45", r.get("amount_of_today"));
|
|
|
|
|
+ assertEquals("100.00", r.get("amount_of_7days"), "被当成分除了 100 就会是 1.00");
|
|
|
|
|
+ assertEquals("0.00", r.get("amount_of_all"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 空表 / 键缺失时是 0.00 而不是 null 或异常 —— 与乐收赢腿同一个不变式。 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void 支付宝收款统计_键缺失时按零() {
|
|
|
|
|
+ when(harOrderMapper.statCollectedAmount(any(), any(), any(), any()))
|
|
|
|
|
+ .thenReturn(Map.of("amount_of_all", new BigDecimal("250.5")));
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> r = service.statHarCollectedAmount(null);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals("0.00", r.get("amount_of_today"));
|
|
|
|
|
+ assertEquals("0.00", r.get("amount_of_7days"));
|
|
|
|
|
+ assertEquals("250.50", r.get("amount_of_all"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 传进 SQL 的状态集合必须**就是** {@code HarOrderStatus.PAY_RECORDED},
|
|
|
|
|
+ * 且那个集合的**内容**是 {@code {PAID}} —— 两件不同的事,都要钉:
|
|
|
|
|
+ * 前者防「两边各写一份」,后者防「往集合里加成员却不改 SQL 的扣减」
|
|
|
|
|
+ * (本通道退款恒为全额 ⇒ 集合里每一行都全额计入;加回「2 退款成功」
|
|
|
|
|
+ * 就会把已经退掉的单当成收款,数字偏大)。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void 支付宝收款统计_状态集合取自PAY_RECORDED() {
|
|
|
|
|
+ when(harOrderMapper.statCollectedAmount(any(), any(), any(), any()))
|
|
|
|
|
+ .thenReturn(Map.of("amount_of_today", BigDecimal.ZERO,
|
|
|
|
|
+ "amount_of_7days", BigDecimal.ZERO, "amount_of_all", BigDecimal.ZERO));
|
|
|
|
|
+
|
|
|
|
|
+ service.statHarCollectedAmount(null);
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ ArgumentCaptor<Collection<String>> captor = ArgumentCaptor.forClass(Collection.class);
|
|
|
|
|
+ verify(harOrderMapper).statCollectedAmount(isNull(), captor.capture(), any(), any());
|
|
|
|
|
+ assertEquals(Set.copyOf(HarOrderStatus.PAY_RECORDED), Set.copyOf(captor.getValue()));
|
|
|
|
|
+ assertEquals(Set.of(HarOrderStatus.PAID), Set.copyOf(captor.getValue()),
|
|
|
|
|
+ "净额口径 = 只算支付成功;加成员必须同时给 SQL 补扣减");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 租户参数**照旧透传**(正面控制):去掉企业筛选时不能顺手把租户筛也去掉。 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void 支付宝收款统计_租户参数仍然透传() {
|
|
|
|
|
+ when(harOrderMapper.statCollectedAmount(any(), any(), any(), any()))
|
|
|
|
|
+ .thenReturn(Map.of("amount_of_today", BigDecimal.ZERO,
|
|
|
|
|
+ "amount_of_7days", BigDecimal.ZERO, "amount_of_all", BigDecimal.ZERO));
|
|
|
|
|
+
|
|
|
|
|
+ service.statHarCollectedAmount(7L);
|
|
|
|
|
+
|
|
|
|
|
+ ArgumentCaptor<Long> tenant = ArgumentCaptor.forClass(Long.class);
|
|
|
|
|
+ verify(harOrderMapper).statCollectedAmount(tenant.capture(), any(), any(), any());
|
|
|
|
|
+ assertEquals(7L, tenant.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 时间窗与另外三块同一口径(北京时间当日零点,近 7 日 = 今日减 7 天)。
|
|
|
|
|
+ * 这几行是**另一份拷贝**(每条腿各算各的)⇒ 单独钉一次,别靠乐收赢那条腿替它作证。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void 支付宝收款统计_时间窗是北京时间当日零点() {
|
|
|
|
|
+ when(harOrderMapper.statCollectedAmount(any(), any(), any(), any()))
|
|
|
|
|
+ .thenReturn(Map.of("amount_of_today", BigDecimal.ZERO,
|
|
|
|
|
+ "amount_of_7days", BigDecimal.ZERO, "amount_of_all", BigDecimal.ZERO));
|
|
|
|
|
+
|
|
|
|
|
+ service.statHarCollectedAmount(null);
|
|
|
|
|
+
|
|
|
|
|
+ ArgumentCaptor<OffsetDateTime> today = ArgumentCaptor.forClass(OffsetDateTime.class);
|
|
|
|
|
+ ArgumentCaptor<OffsetDateTime> weekAgo = ArgumentCaptor.forClass(OffsetDateTime.class);
|
|
|
|
|
+ verify(harOrderMapper).statCollectedAmount(isNull(), any(),
|
|
|
|
|
+ today.capture(), weekAgo.capture());
|
|
|
|
|
+ assertEquals(ZoneOffset.ofHours(8), today.getValue().getOffset());
|
|
|
|
|
+ assertEquals(0, today.getValue().getHour());
|
|
|
|
|
+ assertEquals(0, today.getValue().getMinute());
|
|
|
|
|
+ assertEquals(0, today.getValue().getSecond());
|
|
|
|
|
+ assertEquals(today.getValue().minusDays(7), weekAgo.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
- * 汇总 = 转账 + 消费 + 乐收赢(净额,分已转元)。
|
|
|
|
|
|
|
+ * 汇总 = 转账 + 消费 + 乐收赢 + 汇安融(用户 2026-10-05 双裁定:第三段换乐收赢、
|
|
|
|
|
+ * 第四段加汇安融)。
|
|
|
*
|
|
*
|
|
|
- * <p>数字是挑过的:乐收赢那 2500 分若不转元,今日会算出 2650.00 而不是 175.00 ——
|
|
|
|
|
- * 这条断言顺带把「汇总里漏了换算」也钉住。7 日/全部同理(3000 分 / 4000 分)。
|
|
|
|
|
|
|
+ * <p>数字是挑过的,每条腿都能自己把自己钉出来:乐收赢那 2500 **分**若不转元,
|
|
|
|
|
+ * 今日会算出 2650.00 而不是 175.00;汇安融那 12.34 **元**若被当成分除 100,
|
|
|
|
|
+ * 今日会算出 162.78 而不是 175.00。两个错误方向相反 ⇒ 一条断言同时钉住两侧。
|
|
|
|
|
+ * 7 日/全部同理(乐收赢 3000/4000 分;汇安融 20/30 元)。
|
|
|
* <p>{@code verifyNoInteractions(f2fTradeRecordMapper)}:当面付那一段是**不再走**,
|
|
* <p>{@code verifyNoInteractions(f2fTradeRecordMapper)}:当面付那一段是**不再走**,
|
|
|
* 不是「走出来的结果恰好为 0」。
|
|
* 不是「走出来的结果恰好为 0」。
|
|
|
*/
|
|
*/
|
|
|
@Test
|
|
@Test
|
|
|
- void 汇总统计_当面付那段换成乐收赢且分已转元() {
|
|
|
|
|
|
|
+ void 汇总统计_转账加消费加乐收赢加汇安融() {
|
|
|
when(transferMapper.statAmounts(any(), any(), any())).thenReturn(Map.of(
|
|
when(transferMapper.statAmounts(any(), any(), any())).thenReturn(Map.of(
|
|
|
"amount_of_today", new BigDecimal("100"),
|
|
"amount_of_today", new BigDecimal("100"),
|
|
|
"amount_of_7days", new BigDecimal("200"),
|
|
"amount_of_7days", new BigDecimal("200"),
|
|
@@ -193,12 +297,16 @@ class AccountServiceStatTest {
|
|
|
"amount_of_today", new BigDecimal("2500"),
|
|
"amount_of_today", new BigDecimal("2500"),
|
|
|
"amount_of_7days", new BigDecimal("3000"),
|
|
"amount_of_7days", new BigDecimal("3000"),
|
|
|
"amount_of_all", new BigDecimal("4000")));
|
|
"amount_of_all", new BigDecimal("4000")));
|
|
|
|
|
+ when(harOrderMapper.statCollectedAmount(any(), any(), any(), any())).thenReturn(Map.of(
|
|
|
|
|
+ "amount_of_today", new BigDecimal("12.34"),
|
|
|
|
|
+ "amount_of_7days", new BigDecimal("20"),
|
|
|
|
|
+ "amount_of_all", new BigDecimal("30")));
|
|
|
|
|
|
|
|
Map<String, String> r = service.statSummaryAmount(null, null, null);
|
|
Map<String, String> r = service.statSummaryAmount(null, null, null);
|
|
|
|
|
|
|
|
- assertEquals("175.00", r.get("amount_of_today")); // 100 + 50 + 25.00
|
|
|
|
|
- assertEquals("290.00", r.get("amount_of_7days")); // 200 + 60 + 30.00
|
|
|
|
|
- assertEquals("410.00", r.get("amount_of_all")); // 300 + 70 + 40.00
|
|
|
|
|
|
|
+ assertEquals("187.34", r.get("amount_of_today")); // 100 + 50 + 25.00 + 12.34
|
|
|
|
|
+ assertEquals("310.00", r.get("amount_of_7days")); // 200 + 60 + 30.00 + 20
|
|
|
|
|
+ assertEquals("440.00", r.get("amount_of_all")); // 300 + 70 + 40.00 + 30
|
|
|
verifyNoInteractions(f2fTradeRecordMapper);
|
|
verifyNoInteractions(f2fTradeRecordMapper);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|