| | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.talentcard.server.business.DatabaseRows; |
| | | import com.talentcard.server.business.payment.huifu.HuifuOnboardingProgressGateway; |
| | | import com.talentcard.server.business.payment.huifu.HuifuPaymentProperties; |
| | | import com.talentcard.server.common.BusinessException; |
| | | import com.talentcard.server.common.ErrorCode; |
| | | import java.util.ArrayList; |
| | |
| | | } |
| | | private final JdbcTemplate jdbc; |
| | | private final HuifuOnboardingProgressGateway gateway; |
| | | public MerchantOnboardingProgressService(JdbcTemplate jdbc, HuifuOnboardingProgressGateway gateway) { this.jdbc=jdbc; this.gateway=gateway; } |
| | | private final HuifuPaymentProperties huifuPaymentProperties; |
| | | public MerchantOnboardingProgressService(JdbcTemplate jdbc, HuifuOnboardingProgressGateway gateway, HuifuPaymentProperties huifuPaymentProperties) { this.jdbc=jdbc; this.gateway=gateway; this.huifuPaymentProperties=huifuPaymentProperties; } |
| | | |
| | | /** |
| | | * 分阶段进度适用于「已关联结算银行卡、且未被整体拒绝」的申请。 |
| | |
| | | } |
| | | |
| | | public Map<String,Object> view(Long id) { return view(application(id)); } |
| | | |
| | | /** 进件来源标记:后台纳管进来的外部进件商户。取值与 V158 的 {@code source} 列一致。 */ |
| | | public static final String SOURCE_MANAGED = "managed"; |
| | | |
| | | /** |
| | | * 这条进件是不是「后台纳管的外部进件」。 |
| | | * |
| | | * <p>它决定这条进件能不能**下发**通道改动:纳管全程只查询、不下发(外部商户的口径已由业务要求 |
| | | * 保持一致,只需核实;而补开微信、绑人才端、改支付场景、开启延迟入账这些下发操作都不幂等, |
| | | * 连锁多店共用同一个汇付商户号时各自下发还会重复下发)。判定只留这一处定义,刷新链路与纳管/解绑 |
| | | * 都读它,避免同一个来源标记在几处各自比较字符串。 |
| | | */ |
| | | public static boolean isManaged(Map<String,Object> application) { |
| | | return SOURCE_MANAGED.equals(text(application.get("source")).trim()); |
| | | } |
| | | |
| | | @org.springframework.transaction.annotation.Transactional(propagation = org.springframework.transaction.annotation.Propagation.NOT_SUPPORTED) |
| | | public Map<String,Object> refresh(Long applicationId, boolean retry) { |
| | |
| | | } |
| | | if (capabilities.containsKey("qry_wx_conf_list")) { |
| | | List<Map<String,Object>> wechat = configurations(capabilities.get("qry_wx_conf_list")); |
| | | boolean valid = List.of("4", "5", "12").stream().allMatch(scene -> wechat.stream().anyMatch(item -> |
| | | scene.equals(text(item.get("pay_scene"))) && decimal(item.get("fee_rate"), "0.70") |
| | | // 口径必须与业务开通的下发取同一份配置(场景、费率、业务开通类型都来自 properties): |
| | | // 判定跟着下发走,否则会出现「通道已开通、阶段永远 missing」,而它下游的收款闸门 |
| | | // 会把商户挡在下单之外。2026-09-24 前这里是硬编码的线上 4/5/12 + 0.70。 |
| | | List<String> expectedScenes = huifuPaymentProperties.merchantWechatSceneList(); |
| | | String expectedFeeRate = huifuPaymentProperties.getMerchantWechatFeeRate().trim(); |
| | | boolean valid = !expectedScenes.isEmpty() && expectedScenes.stream().allMatch(scene -> wechat.stream().anyMatch(item -> |
| | | scene.equals(text(item.get("pay_scene"))) && decimal(item.get("fee_rate"), expectedFeeRate) |
| | | // fee_min_amt 是可选字段,且文档写明「大于 0」——不设最低收费时我们**不下发**它, |
| | | // 通道也就不返回它。缺失必须按 0 处理,否则这条判定永远不成立:商户微信业务 |
| | | // 明明已经开好(三场景、费率、内扣、子商户号都对),阶段却永远停在未确认 |
| | |
| | | Map<String,Object> appConfig = gateway.queryWechatConfig(text(application.get("providerHuifuId"))); |
| | | if (appConfig != null && "00000000".equals(text(appConfig.get("resp_code")))) { |
| | | boolean bound = false, controlled = false; |
| | | // 业务开通类型也取配置:绑定写在哪个 fee_type 下就得在哪个下面找,两边必须同一个值。 |
| | | String expectedFeeType = huifuPaymentProperties.getMerchantWechatFeeType(); |
| | | String expectedAppId = huifuPaymentProperties.getMiniProgramAppId().trim(); |
| | | for (Map<String,Object> config : configurations(appConfig.getOrDefault("atu_config_bo_list", List.of()))) { |
| | | if (!"01".equals(text(config.get("fee_type")))) continue; |
| | | if (!expectedFeeType.equals(text(config.get("fee_type")))) continue; |
| | | for (Map<String,Object> entry : configurations(config.getOrDefault("wx_config_list", List.of()))) { |
| | | if ("1".equals(text(entry.get("config_type"))) && "wxcfee477acb54d179".equals(text(entry.get("config_value")))) { |
| | | if ("1".equals(text(entry.get("config_type"))) && expectedAppId.equals(text(entry.get("config_value")))) { |
| | | bound = true; |
| | | controlled |= "Y".equals(text(entry.get("control_flag"))); |
| | | } |
| | |
| | | if (!"confirmed".equals(stages.get("realname"))) stages.put("realname", "missing"); |
| | | } else jdbc.update("update merchant_onboarding_progress set sync_error=? where application_id=? and sync_owner=?",queryError(realname),applicationId,owner); |
| | | } |
| | | if ("Y".equals(audit) && "S".equals(text(response.get("reg_status"))) && capabilities != null |
| | | if (!isManaged(application) && "Y".equals(audit) && "S".equals(text(response.get("reg_status"))) && capabilities != null |
| | | && "00000000".equals(text(capabilities.get("resp_code"))) && "NORMAL".equals(text(capabilities.get("mer_stat"))) |
| | | && capabilities.containsKey("delay_flag") && capabilities.containsKey("forced_delay_flag")) { |
| | | advanceDelay(applicationId,text(application.get("providerHuifuId")),owner,stages,retry); |