|
@@ -19,6 +19,7 @@ import com.payment.platform.module.payment.har.dto.HarOrderCreateDTO;
|
|
|
import com.payment.platform.module.payment.har.dto.HarOrderVO;
|
|
import com.payment.platform.module.payment.har.dto.HarOrderVO;
|
|
|
import com.payment.platform.module.payment.har.entity.HarMerchantEntity;
|
|
import com.payment.platform.module.payment.har.entity.HarMerchantEntity;
|
|
|
import com.payment.platform.module.payment.har.entity.HarOrderEntity;
|
|
import com.payment.platform.module.payment.har.entity.HarOrderEntity;
|
|
|
|
|
+import com.payment.platform.module.payment.har.entity.HarShopEntity;
|
|
|
import com.payment.platform.module.payment.har.enums.HarOrderStatus;
|
|
import com.payment.platform.module.payment.har.enums.HarOrderStatus;
|
|
|
import com.payment.platform.module.payment.har.mapper.HarOrderMapper;
|
|
import com.payment.platform.module.payment.har.mapper.HarOrderMapper;
|
|
|
import com.payment.platform.module.payment.stream.OrderEventPublisher;
|
|
import com.payment.platform.module.payment.stream.OrderEventPublisher;
|
|
@@ -58,6 +59,7 @@ class HarOrderServiceTest {
|
|
|
|
|
|
|
|
@Mock private HarOrderMapper orderMapper;
|
|
@Mock private HarOrderMapper orderMapper;
|
|
|
@Mock private HarMerchantService merchantService;
|
|
@Mock private HarMerchantService merchantService;
|
|
|
|
|
+ @Mock private HarShopService shopService;
|
|
|
@Mock private HarClient harClient;
|
|
@Mock private HarClient harClient;
|
|
|
@Mock private OrderEventPublisher publisher;
|
|
@Mock private OrderEventPublisher publisher;
|
|
|
|
|
|
|
@@ -65,7 +67,7 @@ class HarOrderServiceTest {
|
|
|
|
|
|
|
|
@BeforeEach
|
|
@BeforeEach
|
|
|
void setUp() {
|
|
void setUp() {
|
|
|
- service = new HarOrderService(orderMapper, merchantService, harClient, publisher);
|
|
|
|
|
|
|
+ service = new HarOrderService(orderMapper, merchantService, shopService, harClient, publisher);
|
|
|
// 初始化 MyBatis-Plus lambda 元数据缓存,使 LambdaQueryWrapper.getSqlSegment() 可在无 Spring 上下文的单测中工作
|
|
// 初始化 MyBatis-Plus lambda 元数据缓存,使 LambdaQueryWrapper.getSqlSegment() 可在无 Spring 上下文的单测中工作
|
|
|
// (否则 getSqlSegment() 抛 MybatisPlusException: can not find lambda cache for this entity。
|
|
// (否则 getSqlSegment() 抛 MybatisPlusException: can not find lambda cache for this entity。
|
|
|
// 同款引导见 LsyCallbackServiceTest / AlipayBatchPayServiceTest。)
|
|
// 同款引导见 LsyCallbackServiceTest / AlipayBatchPayServiceTest。)
|
|
@@ -327,6 +329,141 @@ class HarOrderServiceTest {
|
|
|
verify(harClient, never()).precreate(any());
|
|
verify(harClient, never()).precreate(any());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ==================== 下单:按地区挑店(开放 API 直传地区码,2026-10-09) ====================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 传了省/市码、没传任何门店码 ⇒ 平台替调用方挑店;挑中的店要**同时**进本地记录
|
|
|
|
|
+ * 与上游请求 —— 只进一处都是半截:本地有、上游没有 = 风控不认;上游有、本地没有 =
|
|
|
|
|
+ * 页面上看不出这单挂的哪家店。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void create_withRegionCodes_picksShopAndFillsThirdShopCode() {
|
|
|
|
|
+ HarOrderCreateDTO dto = dto();
|
|
|
|
|
+ dto.setProvinceCode("32");
|
|
|
|
|
+ dto.setCityCode("3201");
|
|
|
|
|
+ HarShopEntity picked = new HarShopEntity();
|
|
|
|
|
+ picked.setThirdShopId("NJ");
|
|
|
|
|
+ when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
|
|
+ // 精确参数 = 同时钉住散列种子就是 out_biz_no(种子若被换掉,这条会因参数不匹配拿到 null 而红)
|
|
|
|
|
+ when(shopService.pickForOrder("M001", "32", "3201", "BIZ-0001")).thenReturn(picked);
|
|
|
|
|
+ when(harClient.precreate(any())).thenReturn(response("10000", precreateDetail()));
|
|
|
|
|
+
|
|
|
|
|
+ service.create(dto, "E1", "KEY1");
|
|
|
|
|
+
|
|
|
|
|
+ ArgumentCaptor<HarOrderEntity> entity = ArgumentCaptor.forClass(HarOrderEntity.class);
|
|
|
|
|
+ verify(orderMapper).insert(entity.capture());
|
|
|
|
|
+ assertEquals("NJ", entity.getValue().getThirdShopCode(), "挑中的店要进本地记录");
|
|
|
|
|
+ assertNull(entity.getValue().getShopCode(), "只填 third_shop_code(与收款链接腿一致),不编造平台门店码");
|
|
|
|
|
+
|
|
|
|
|
+ ArgumentCaptor<HarPrecreateRequest> req = ArgumentCaptor.forClass(HarPrecreateRequest.class);
|
|
|
|
|
+ verify(harClient).precreate(req.capture());
|
|
|
|
|
+ assertEquals("NJ", req.getValue().getThirdShopCode(), "挑中的店必须真的发到上游");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 被扫腿同款接线:builder 改了一个、漏了另一个的话,这条红而上面那条绿 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void create_scanMode_alsoSendsThePickedShop() {
|
|
|
|
|
+ HarOrderCreateDTO dto = dto();
|
|
|
|
|
+ dto.setAuthCode("280812345678901234");
|
|
|
|
|
+ dto.setProvinceCode("32");
|
|
|
|
|
+ HarShopEntity picked = new HarShopEntity();
|
|
|
|
|
+ picked.setThirdShopId("NJ");
|
|
|
|
|
+ when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
|
|
+ when(shopService.pickForOrder("M001", "32", null, "BIZ-0001")).thenReturn(picked);
|
|
|
|
|
+ when(harClient.pay(any())).thenReturn(response("10000", precreateDetail()));
|
|
|
|
|
+
|
|
|
|
|
+ service.create(dto, "E1", "KEY1");
|
|
|
|
|
+
|
|
|
|
|
+ ArgumentCaptor<HarPayRequest> req = ArgumentCaptor.forClass(HarPayRequest.class);
|
|
|
|
|
+ verify(harClient).pay(req.capture());
|
|
|
|
|
+ assertEquals("NJ", req.getValue().getThirdShopCode(), "挑中的店必须真的发到上游");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 显式门店码优先:给了 third_shop_code 就不再挑(挑出来的会覆盖调用方明确指定的店) */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void create_explicitThirdShopCode_beatsRegionCodes() {
|
|
|
|
|
+ HarOrderCreateDTO dto = dto();
|
|
|
|
|
+ dto.setThirdShopCode("EXPLICIT");
|
|
|
|
|
+ dto.setProvinceCode("32");
|
|
|
|
|
+ when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
|
|
+ when(harClient.precreate(any())).thenReturn(response("10000", precreateDetail()));
|
|
|
|
|
+
|
|
|
|
|
+ service.create(dto, "E1", "KEY1");
|
|
|
|
|
+
|
|
|
|
|
+ verifyNoInteractions(shopService);
|
|
|
|
|
+ ArgumentCaptor<HarOrderEntity> entity = ArgumentCaptor.forClass(HarOrderEntity.class);
|
|
|
|
|
+ verify(orderMapper).insert(entity.capture());
|
|
|
|
|
+ assertEquals("EXPLICIT", entity.getValue().getThirdShopCode());
|
|
|
|
|
+ ArgumentCaptor<HarPrecreateRequest> req = ArgumentCaptor.forClass(HarPrecreateRequest.class);
|
|
|
|
|
+ verify(harClient).precreate(req.capture());
|
|
|
|
|
+ assertEquals("EXPLICIT", req.getValue().getThirdShopCode(), "上游发的必须是显式指定的那个,不是挑的");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 显式门店码优先:给了 shop_code 同样不挑 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void create_explicitShopCode_beatsRegionCodes() {
|
|
|
|
|
+ HarOrderCreateDTO dto = dto();
|
|
|
|
|
+ dto.setShopCode("S-PLATFORM");
|
|
|
|
|
+ dto.setProvinceCode("32");
|
|
|
|
|
+ when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
|
|
+ when(harClient.precreate(any())).thenReturn(response("10000", precreateDetail()));
|
|
|
|
|
+
|
|
|
|
|
+ service.create(dto, "E1", "KEY1");
|
|
|
|
|
+
|
|
|
|
|
+ verifyNoInteractions(shopService);
|
|
|
|
|
+ ArgumentCaptor<HarOrderEntity> entity = ArgumentCaptor.forClass(HarOrderEntity.class);
|
|
|
|
|
+ verify(orderMapper).insert(entity.capture());
|
|
|
|
|
+ assertEquals("S-PLATFORM", entity.getValue().getShopCode());
|
|
|
|
|
+ assertNull(entity.getValue().getThirdShopCode(), "没挑店就不该凭空多出一个 third_shop_code");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 挑不出来 ⇒ 400,且**不留本地单、不打上游、不发订单信号** —— 与收款链接腿
|
|
|
|
|
+ * 「挑不出来就拒收」同一口径。静默退回「不带门店下单」等于调用方明确要求的
|
|
|
|
|
+ * 地区归属被悄悄丢掉。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void create_regionPickRejected_400_beforePersistAndUpstream() {
|
|
|
|
|
+ HarOrderCreateDTO dto = dto();
|
|
|
|
|
+ dto.setProvinceCode("32");
|
|
|
|
|
+ when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
|
|
+ when(shopService.pickForOrder("M001", "32", null, "BIZ-0001"))
|
|
|
|
|
+ .thenThrow(new BusinessException(400, "该地区没有可用门店: 省 32"));
|
|
|
|
|
+
|
|
|
|
|
+ BusinessException e = assertThrows(BusinessException.class, () -> service.create(dto, "E1", "KEY1"));
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(400, e.getCode());
|
|
|
|
|
+ verify(orderMapper, never()).insert(any());
|
|
|
|
|
+ verify(harClient, never()).precreate(any());
|
|
|
|
|
+ verify(publisher, never()).changed(any(), any());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 什么都不传 = 现状:一个字段都不碰,不去问挑店 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void create_noRegionFields_neverConsultsShopService() {
|
|
|
|
|
+ when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
|
|
+ when(harClient.precreate(any())).thenReturn(response("10000", precreateDetail()));
|
|
|
|
|
+
|
|
|
|
|
+ service.create(dto(), "E1", "KEY1");
|
|
|
|
|
+
|
|
|
|
|
+ verifyNoInteractions(shopService);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 只给 city_code 也是「地区意图」——进入挑店并吃它那句 400,而不是被静默忽略 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void create_cityCodeWithoutProvince_entersPickAndPropagates400() {
|
|
|
|
|
+ HarOrderCreateDTO dto = dto();
|
|
|
|
|
+ dto.setCityCode("3201");
|
|
|
|
|
+ when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
|
|
|
|
|
+ when(shopService.pickForOrder("M001", null, "3201", "BIZ-0001"))
|
|
|
|
|
+ .thenThrow(new BusinessException(400, "省份编码不能为空: 传了 city_code 就必须一并传 province_code"));
|
|
|
|
|
+
|
|
|
|
|
+ BusinessException e = assertThrows(BusinessException.class, () -> service.create(dto, "E1", "KEY1"));
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(400, e.getCode());
|
|
|
|
|
+ verify(orderMapper, never()).insert(any());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// ==================== 主动查询 ====================
|
|
// ==================== 主动查询 ====================
|
|
|
|
|
|
|
|
/**
|
|
/**
|