package com.talentcard.server.business.merchant;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.mockito.ArgumentMatchers.*;
|
import static org.mockito.Mockito.*;
|
import java.util.Map;
|
import com.talentcard.server.business.merchant.auth.MerchantPrincipal;
|
import com.talentcard.server.business.payment.huifu.HuifuOnboardingProgressGateway;
|
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;
|
|
@SpringBootTest
|
@ActiveProfiles("test")
|
class MerchantExistingOnboardingTests {
|
@Autowired JdbcTemplate jdbc;
|
@Autowired MerchantSettlementService service;
|
@MockitoBean HuifuOnboardingProgressGateway gateway;
|
// 刷新现在会先推进开通步骤(微信配置、业务开通),所以这两个网关也要挡住,
|
// 否则测试会真的打到汇付。
|
@MockitoBean com.talentcard.server.business.payment.huifu.HuifuMerchantWechatConfigGateway wechatConfigGateway;
|
@MockitoBean com.talentcard.server.business.payment.huifu.HuifuMerchantBusinessOpenGateway businessOpenGateway;
|
@Autowired org.springframework.web.context.WebApplicationContext context;
|
@Autowired com.talentcard.server.business.admin.auth.AdminAuthService adminAuth;
|
|
@Test
|
void successfulDelayCallbackIgnoresLateFailureForTheSameAttempt() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","mer_stat","NORMAL","delay_flag","N","forced_delay_flag","N"));
|
when(gateway.configureDelay(anyString(),anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_no","delay-apply"));
|
service.refreshOnboarding(principal,false);
|
var number=org.mockito.ArgumentCaptor.forClass(String.class);
|
var date=org.mockito.ArgumentCaptor.forClass(String.class);
|
verify(gateway).configureDelay(anyString(),number.capture(),date.capture());
|
assertThat(service.handleBusinessOpenCallback(Map.of("huifu_id","6666000241335656","req_seq_id",number.getValue(),"req_date",date.getValue(),"audit_status","Y","sub_resp_code","00000000"))).isTrue();
|
assertThat(service.handleBusinessOpenCallback(Map.of("huifu_id","6666000241335656","req_seq_id",number.getValue(),"req_date",date.getValue(),"audit_status","N","sub_resp_code","99999999"))).isTrue();
|
assertThat(service.refreshOnboarding(principal,true)).containsEntry("retryable",false);
|
verify(gateway,times(1)).configureDelay(anyString(),anyString(),anyString());
|
}
|
|
@Test
|
void completesOnlyAfterEveryCapabilityAndTheOriginalCardAreConfirmed() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
jdbc.update("update merchant_bank_card set card_hash=? where merchant_id=?",sha256("622200001234"),principal.merchantId());
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S","bind_card_status","S"));
|
var scenes=java.util.List.of("4","5","12").stream().map(scene -> Map.of("pay_scene",scene,"fee_rate","0.70","fee_min_amt","0.00","fee_charge_type","1","out_fee_flag","2",
|
"wx_mer_infos",java.util.List.of(Map.of("bank_mer_code","wx-merchant","pay_channel_id","00016904","is_default","1")))).toList();
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","mer_stat","NORMAL","delay_flag","Y","forced_delay_flag","N","qry_wx_conf_list",scenes,
|
"qry_settle_config_list",java.util.List.of(Map.of("settle_status","1","settle_cycle","D1","settle_pattern","P0","min_amt","0","remained_amt","0","token_no","original-card-token")),
|
"qry_cash_card_info_list",java.util.List.of(Map.of("card_no","622200001234","token_no","original-card-token"))));
|
when(gateway.queryWechatConfig(anyString())).thenReturn(Map.of("resp_code","00000000","atu_config_bo_list",java.util.List.of(Map.of("fee_type","01","wx_config_list",java.util.List.of(Map.of("config_type","1","config_value","wxcfee477acb54d179"))))));
|
when(gateway.queryRealname(anyString())).thenReturn(Map.of("resp_code","00000000","applyment_stat","APPLYMENT_STATE_PASSED","authorize_stat","1"));
|
assertThat(service.refreshOnboarding(principal,false)).containsEntry("status","approved");
|
assertThat(service.onboardingStatus(principal)).containsEntry("status","approved");
|
assertThat(service.bankCards(principal)).hasSize(1);
|
verify(gateway,never()).configureDelay(anyString(),anyString(),anyString());
|
}
|
|
private static String sha256(String value) {
|
try { return java.util.HexFormat.of().formatHex(java.security.MessageDigest.getInstance("SHA-256").digest(value.getBytes(java.nio.charset.StandardCharsets.UTF_8))); }
|
catch (Exception exception) { throw new IllegalStateException(exception); }
|
}
|
|
@Test
|
void concurrentRefreshCannotCreateASecondDelayAttempt() throws Exception {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","mer_stat","NORMAL","delay_flag","N","forced_delay_flag","N"));
|
var entered=new java.util.concurrent.CountDownLatch(1);
|
var release=new java.util.concurrent.CountDownLatch(1);
|
when(gateway.configureDelay(anyString(),anyString(),anyString())).thenAnswer(call -> {
|
entered.countDown();
|
if (!release.await(10,java.util.concurrent.TimeUnit.SECONDS)) throw new IllegalStateException("Test release timed out");
|
return Map.of("resp_code","00000000","apply_no","concurrent-apply");
|
});
|
var executor=java.util.concurrent.Executors.newSingleThreadExecutor();
|
try {
|
var first=executor.submit(() -> service.refreshOnboarding(principal,false));
|
assertThat(entered.await(10,java.util.concurrent.TimeUnit.SECONDS)).isTrue();
|
service.refreshOnboarding(principal,true);
|
release.countDown();
|
first.get(10,java.util.concurrent.TimeUnit.SECONDS);
|
verify(gateway,times(1)).configureDelay(anyString(),anyString(),anyString());
|
} finally { release.countDown(); executor.shutdownNow(); }
|
}
|
|
@Test
|
void retriesOnlyAnExplicitlyFailedDelayAttemptWithANewRequestNumber() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","mer_stat","NORMAL","delay_flag","N","forced_delay_flag","N"));
|
when(gateway.configureDelay(anyString(),anyString(),anyString())).thenReturn(Map.of("resp_code","20002"),Map.of("resp_code","00000000","apply_no","retry-apply"));
|
assertThat(service.refreshOnboarding(principal,false)).containsEntry("retryable",true);
|
service.refreshOnboarding(principal,false);
|
service.refreshOnboarding(principal,true);
|
var requests=org.mockito.ArgumentCaptor.forClass(String.class);
|
verify(gateway,times(2)).configureDelay(eq("6666000241335656"),requests.capture(),anyString());
|
assertThat(requests.getAllValues()).doesNotHaveDuplicates();
|
}
|
|
@Test
|
void oldBasicFailureCallbackCannotRejectAnExistingVerifiedMerchantOrCard() {
|
Map<String,Object> fixture=fixture();
|
MerchantPrincipal principal=(MerchantPrincipal)fixture.get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","delay_flag","Y","forced_delay_flag","N"));
|
service.refreshOnboarding(principal,false);
|
service.handleOnboardingCallback(Map.of("req_seq_id",fixture.get("applicationNo"),"huifu_id","6666000241335656","audit_status","N","sub_resp_code","99999999"));
|
assertThat(service.bankCards(principal)).allSatisfy(card -> assertThat(card.get("status")).isEqualTo("approved"));
|
assertThat(service.onboardingStatus(principal)).containsEntry("status","pending");
|
}
|
|
@Test
|
void unknownDelayUpdateIsQueriedBeforeRetryAndNeverSubmittedTwice() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","mer_stat","NORMAL","delay_flag","N","forced_delay_flag","N"));
|
when(gateway.configureDelay(anyString(),anyString(),anyString())).thenThrow(new com.talentcard.server.business.payment.huifu.HuifuPaymentGatewayException("timeout",true,null));
|
service.refreshOnboarding(principal,false);
|
Map<String,Object> result=service.refreshOnboarding(principal,true);
|
verify(gateway,times(1)).configureDelay(eq("6666000241335656"),matches("[a-f0-9]{32}"),matches("[0-9]{8}"));
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","delay").containsEntry("status","pending"));
|
assertThat(result).containsEntry("retryable",false).containsEntry("status","pending");
|
}
|
|
@Test
|
void failedRealnameQueryPreservesConfirmedStateAndReportsQueryFailure() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","delay_flag","Y","forced_delay_flag","N"));
|
when(gateway.queryRealname(anyString())).thenReturn(Map.of("resp_code","00000000","applyment_stat","APPLYMENT_STATE_PASSED","authorize_stat","1"),Map.of("resp_code","20002"));
|
service.refreshOnboarding(principal,false);
|
Map<String,Object> result=service.refreshOnboarding(principal,false);
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","realname").containsEntry("status","confirmed"));
|
assertThat(result.get("syncError")).isEqualTo("汇付查询权限不足(20002),请联系平台运营核实接口权限");
|
}
|
|
@Test
|
void realnamePassedStillNeedsAuthorizationAndControlledAppIdIsNotReady() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","delay_flag","Y","forced_delay_flag","N"));
|
when(gateway.queryWechatConfig(anyString())).thenReturn(Map.of("resp_code","00000000","atu_config_bo_list",java.util.List.of(Map.of("fee_type","01","wx_applet_app_id","wxcfee477acb54d179","wx_config_list",java.util.List.of(Map.of("config_type","1","config_value","wxcfee477acb54d179","control_flag","Y"))))));
|
when(gateway.queryRealname(anyString())).thenReturn(Map.of("resp_code","00000000","applyment_stat","APPLYMENT_STATE_PASSED","authorize_stat","0"));
|
Map<String,Object> result=service.refreshOnboarding(principal,false);
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","appId").containsEntry("status","action_required"));
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","realname").containsEntry("status","action_required"));
|
assertThat(result).containsEntry("status","pending");
|
}
|
|
@Test
|
void ordinaryReadsExposeSameProgressWithoutCallingHuifu() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","delay_flag","N","forced_delay_flag","N"));
|
Map<String,Object> refreshed=service.refreshOnboarding(principal,false);
|
clearInvocations(gateway);
|
assertThat(service.onboardingStatus(principal).get("stages")).isEqualTo(refreshed.get("stages"));
|
verifyNoInteractions(gateway);
|
}
|
|
@Test
|
void refreshRequiresSettlementEditPermissionEvenForAnAuthenticatedAdmin() throws Exception {
|
String account="t02admin"+System.nanoTime();
|
jdbc.update("insert into admin_operator(account,password_hash,display_name,role_code,status,deleted) values (?,?,?,'t02-no-rights',1,0)",account,
|
com.talentcard.server.business.merchant.auth.MerchantPasswordHasher.encode("TestPass123"),account);
|
String token=String.valueOf(adminAuth.login(new com.talentcard.server.business.admin.auth.AdminLoginRequest(account,"TestPass123"),account).get("accessToken"));
|
org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup(context).build().perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post("/api/admin/management/merchant-settlement/onboardings/449/progress/refresh")
|
.header("Authorization","Bearer "+token)).andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.status().isForbidden());
|
verifyNoInteractions(gateway);
|
}
|
|
@Test
|
void configuredWechatScenesStillRequireTalentAppAndAuthorization() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
var scenes=java.util.List.of("4","5","12").stream().map(scene -> Map.of("pay_scene",scene,"fee_rate","0.70","fee_min_amt","0.00","fee_charge_type","1","out_fee_flag","2",
|
"wx_mer_infos",java.util.List.of(Map.of("bank_mer_code","wx-merchant","pay_channel_id","00016904","is_default","1")))).toList();
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","mer_stat","NORMAL","delay_flag","Y","forced_delay_flag","N","qry_wx_conf_list",scenes));
|
Map<String,Object> result=service.refreshOnboarding(principal,false);
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","wechat").containsEntry("status","confirmed"));
|
assertThat(result).containsEntry("status","pending");
|
}
|
|
@Test
|
void confirmsD1OnlyFromEffectiveZeroThresholdBatchSettlement() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","mer_stat","NORMAL","delay_flag","Y","forced_delay_flag","N",
|
"qry_settle_config_list","[{\"settle_status\":\"1\",\"settle_cycle\":\"D1\",\"settle_pattern\":\"P0\",\"min_amt\":0,\"remained_amt\":0}]"));
|
Map<String,Object> result=service.refreshOnboarding(principal,false);
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","settlement").containsEntry("status","confirmed"));
|
assertThat(result).containsEntry("status","pending");
|
}
|
|
@Test
|
void submittingAgainCannotReplaceDesignatedExistingMerchantAfterConfigFailure() {
|
Map<String,Object> application=fixture();
|
jdbc.update("update merchant_onboarding_application set wechat_config_status='failed' where application_no=?",application.get("applicationNo"));
|
Map<String,Object> result=service.submitOnboarding((MerchantPrincipal)application.get("principal"),null,"T02-retry-"+System.nanoTime());
|
assertThat(result.get("applicationNo")).isEqualTo(application.get("applicationNo"));
|
}
|
|
@Test
|
void missingDelayFieldsKeepLastConfirmedEvidenceAndReportIncompleteQuery() {
|
MerchantPrincipal principal=(MerchantPrincipal)fixture().get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","delay_flag","Y","forced_delay_flag","N"),Map.of("resp_code","00000000"));
|
service.refreshOnboarding(principal,false);
|
Map<String,Object> result=service.refreshOnboarding(principal,false);
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","delay").containsEntry("status","confirmed"));
|
assertThat(result.get("syncError")).isEqualTo("汇付返回缺少延迟配置字段,已保留最后有效状态");
|
}
|
|
@Test
|
void capabilityQueryErrorKeepsConfirmedDelayAndReportsSyncError() {
|
Map<String,Object> application=fixture();
|
MerchantPrincipal principal=(MerchantPrincipal)application.get("principal");
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","delay_flag","Y","forced_delay_flag","N"),Map.of("resp_code","20002","resp_desc","数据权限认证失败"));
|
service.refreshOnboarding(principal,false);
|
Map<String,Object> result=service.refreshOnboarding(principal,false);
|
assertThat(result.get("syncError")).isEqualTo("汇付查询权限不足(20002),请联系平台运营核实接口权限");
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","delay").containsEntry("status","confirmed"));
|
}
|
|
@Test
|
void approvedBasicApplicationIsNotCompleteWithoutWechatAndFundsEvidence() {
|
Map<String,Object> application=fixture();
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S","bind_card_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","mer_stat","NORMAL"));
|
Map<String,Object> result=service.refreshOnboarding((MerchantPrincipal)application.get("principal"),false);
|
assertThat(result).containsEntry("status","pending");
|
assertThat((java.util.List<Map<String,Object>>)result.get("stages")).anySatisfy(stage -> assertThat(stage).containsEntry("key","basic").containsEntry("status","confirmed"));
|
assertThat(result.get("lastSyncedAt")).isNotEqualTo("");
|
}
|
|
private Map<String,Object> fixture() {
|
String key="T02-"+System.nanoTime();
|
jdbc.update("insert into merchant(merchant_name,status,deleted) values (?,1,0)",key);
|
Long merchant=jdbc.queryForObject("select id from merchant where merchant_name=?",Long.class,key);
|
jdbc.update("insert into merchant_store(merchant_id,store_name,status,deleted) values (?,?,1,0)",merchant,key);
|
Long store=jdbc.queryForObject("select id from merchant_store where merchant_id=?",Long.class,merchant);
|
jdbc.update("insert into merchant_bank_card(merchant_id,store_id,bank_name,branch_name,city_code,card_tail,card_masked,card_hash,status,idempotency_key) values (?,?,'Bank','Branch','510100','1234','****1234',?,'approved',?)",merchant,store,key,key);
|
Long card=jdbc.queryForObject("select id from merchant_bank_card where idempotency_key=?",Long.class,key);
|
jdbc.update("insert into merchant_onboarding_application(merchant_id,store_id,application_no,subject_type,subject_name,contact_name,contact_mobile,idempotency_key,status,provider_huifu_id,provider_apply_no,initial_bank_card_id,wechat_config_status,business_open_status) values (?, ?, ?, 'enterprise','Owner','Owner','13700000000',?,'pending','6666000241335656','original-apply',?,'approved','approved')",merchant,store,key,key,card);
|
return Map.of("principal",new MerchantPrincipal(1L,merchant,store,"owner"),"applicationNo",key);
|
}
|
|
/**
|
* 分阶段刷新要**先推进开通步骤,再回报结果**。渠道侧的微信小程序支付配置与商户业务开通原本
|
* 只由进件审核回调触发,而分阶段申请的回调走的是进度刷新——不在这里补上推进,新商户的微信
|
* 开通就永远不会被发起。
|
*/
|
@Test
|
void refreshingAdvancesTheWechatProvisioningSteps() {
|
Map<String,Object> fixture=provisioningFixture();
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","delay_flag","Y","forced_delay_flag","N"));
|
when(gateway.queryWechatConfig(anyString())).thenReturn(Map.of("resp_code","00000000"));
|
when(wechatConfigGateway.configure(anyString(),anyString())).thenReturn(new com.talentcard.server.business.payment.huifu.HuifuMerchantWechatConfigGateway.Result(true,"00000000","成功"));
|
when(businessOpenGateway.open(anyString(),anyString(),anyString(),anyString(),anyBoolean())).thenReturn(new com.talentcard.server.business.payment.huifu.HuifuMerchantBusinessOpenGateway.Result(true,"BO-APPLY","90000000","审核中"));
|
|
service.refreshOnboarding((MerchantPrincipal)fixture.get("principal"),false);
|
|
assertThat(statusOf("wechat_config_status",String.valueOf(fixture.get("applicationNo")))).isEqualTo("approved");
|
assertThat(statusOf("business_open_status",String.valueOf(fixture.get("applicationNo")))).isEqualTo("processing");
|
verify(wechatConfigGateway,times(1)).configure(anyString(),eq("6666000241335656"));
|
verify(businessOpenGateway,times(1)).open(anyString(),eq("6666000241335656"),anyString(),anyString(),anyBoolean());
|
}
|
|
/**
|
* 通道侧已经生效的步骤不再重复提交。微信商户配置是**添加**接口(成功后不支持改删、每种参数
|
* 最多 5 组),业务开通是申请型接口,重复提交会在通道留下重复配置或第二份申请。
|
*/
|
@Test
|
void refreshingSkipsStepsAlreadyEffectiveOnTheChannel() {
|
Map<String,Object> fixture=provisioningFixture();
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","00000000","apply_status","Y","reg_status","S"));
|
when(gateway.queryCapabilities(anyString())).thenReturn(Map.of("resp_code","00000000","delay_flag","Y","forced_delay_flag","N",
|
"qry_wx_conf_list",java.util.List.of(Map.of("pay_scene","4","fee_rate","0.70"))));
|
when(gateway.queryWechatConfig(anyString())).thenReturn(Map.of("resp_code","00000000","atu_config_bo_list",
|
java.util.List.of(Map.of("fee_type","01","wx_config_list",java.util.List.of(Map.of("config_type","1","config_value","wxcfee477acb54d179"))))));
|
|
service.refreshOnboarding((MerchantPrincipal)fixture.get("principal"),false);
|
|
assertThat(statusOf("wechat_config_status",String.valueOf(fixture.get("applicationNo")))).isEqualTo("approved");
|
assertThat(statusOf("business_open_status",String.valueOf(fixture.get("applicationNo")))).isEqualTo("approved");
|
verify(wechatConfigGateway,never()).configure(anyString(),anyString());
|
verify(businessOpenGateway,never()).open(anyString(),anyString(),anyString(),anyString(),anyBoolean());
|
}
|
|
private String statusOf(String column,String applicationNo) {
|
return jdbc.queryForObject("select "+column+" from merchant_onboarding_application where application_no=?",String.class,applicationNo);
|
}
|
|
/** 建一张还停在 not_started 的进件,用来观察刷新会不会去推进开通步骤。 */
|
private Map<String,Object> provisioningFixture() {
|
String key="T02P-"+System.nanoTime();
|
jdbc.update("insert into merchant(merchant_name,status,deleted) values (?,1,0)",key);
|
Long merchant=jdbc.queryForObject("select id from merchant where merchant_name=?",Long.class,key);
|
jdbc.update("insert into merchant_store(merchant_id,store_name,status,deleted) values (?,?,1,0)",merchant,key);
|
Long store=jdbc.queryForObject("select id from merchant_store where merchant_id=?",Long.class,merchant);
|
jdbc.update("insert into merchant_bank_card(merchant_id,store_id,bank_name,branch_name,city_code,card_tail,card_masked,card_hash,status,idempotency_key) values (?,?,'Bank','Branch','510100','1234','****1234',?,'approved',?)",merchant,store,key,key);
|
Long card=jdbc.queryForObject("select id from merchant_bank_card where idempotency_key=?",Long.class,key);
|
jdbc.update("insert into merchant_onboarding_application(merchant_id,store_id,application_no,subject_type,subject_name,contact_name,contact_mobile,idempotency_key,status,provider_huifu_id,provider_apply_no,initial_bank_card_id) values (?, ?, ?, 'enterprise','Owner','Owner','13700000000',?,'pending','6666000241335656','original-apply',?)",merchant,store,key,key,card);
|
return Map.of("principal",new MerchantPrincipal(1L,merchant,store,"owner"),"applicationNo",key);
|
}
|
|
@Test
|
void queryPermissionErrorPreservesOriginalApplicationAndCard() {
|
String key = "T02-" + System.nanoTime();
|
jdbc.update("insert into merchant(merchant_name,status,deleted) values (?,1,0)", key);
|
Long merchant = jdbc.queryForObject("select id from merchant where merchant_name=?", Long.class,key);
|
jdbc.update("insert into merchant_store(merchant_id,store_name,status,deleted) values (?,?,1,0)",merchant,key);
|
Long store = jdbc.queryForObject("select id from merchant_store where merchant_id=?",Long.class,merchant);
|
jdbc.update("insert into merchant_bank_card(merchant_id,store_id,bank_name,branch_name,city_code,card_tail,card_masked,card_hash,status,idempotency_key) values (?,?,'Bank','Branch','510100','1234','****1234',?,'approved',?)",merchant,store,key,key);
|
Long card = jdbc.queryForObject("select id from merchant_bank_card where idempotency_key=?",Long.class,key);
|
jdbc.update("insert into merchant_onboarding_application(merchant_id,store_id,application_no,subject_type,subject_name,contact_name,contact_mobile,idempotency_key,status,provider_huifu_id,provider_apply_no,initial_bank_card_id,wechat_config_status,business_open_status) values (?, ?, ?, 'enterprise','Owner','Owner','13700000000',?,'pending','6666000241335656','original-apply',?,'approved','approved')",merchant,store,key,key,card);
|
when(gateway.queryApplication(anyString(),anyString())).thenReturn(Map.of("resp_code","20002","resp_desc","数据权限认证失败"));
|
Map<String,Object> result=service.refreshOnboarding(new MerchantPrincipal(1L,merchant,store,"owner"),false);
|
assertThat(result).containsEntry("applicationNo",key).containsEntry("status","pending").containsEntry("syncError","汇付查询权限不足(20002),请联系平台运营核实接口权限");
|
assertThat(service.bankCards(new MerchantPrincipal(1L,merchant,store,"owner"))).isNotNull();
|
assertThat(jdbc.queryForObject("select initial_bank_card_id from merchant_onboarding_application where application_no=?",Long.class,key)).isEqualTo(card);
|
}
|
}
|