| | |
| | | * |
| | | * <p>为什么不新造链路:纳管产出的就是一条进件记录。它复用既有的进件记录、六阶段进度、收款能力快照 |
| | | * 与收款就绪判定,只在「写入口」和「来源标记」两处新增——{@code provider_huifu_id} 原先三个写入口 |
| | | * 全部来自汇付回执,纳管是第四个(见 V157 的 source 列)。 |
| | | * 全部来自汇付回执,纳管是第四个(见 V158 的 source 列)。 |
| | | * |
| | | * <p><b>全程只查询、不下发。</b> 外部进件商户的口径已由业务要求与自助进件保持一致,纳管只需核实; |
| | | * 而补开微信、绑人才端 AppID、改支付场景、开启延迟入账这些下发操作都不幂等(改支付场景会重建微信 |
| | |
| | | * |
| | | * <p>查不到就明确拒绝——那说明该商户不在我们**渠道商**的管辖范围内,纳管进行不下去。注意把商户 |
| | | * 改成挂在平台自己名下既解决不了权限失败、还会平白多出材料要求,不是「迁一下归属就能解决」的事。 |
| | | * |
| | | * <p>已经填了入驻申请单号的话,顺带把 `(商户号, 申请单号)` 配一次对(见 {@link #applyNoMismatch}): |
| | | * 错号在提交时才被拒,运营会白填一屏还看不出哪一项有问题。 |
| | | */ |
| | | public Map<String, Object> preview(Long merchantId, String huifuId) { |
| | | public Map<String, Object> preview(Long merchantId, String huifuId, String applyNo) { |
| | | MerchantRef merchant = requireMerchant(merchantId); |
| | | String providerHuifuId = requireHuifuId(huifuId); |
| | | Map<String, Object> channel = channelMerchant(providerHuifuId); |
| | | Long storeId = primaryStore(merchantId); |
| | | Map<String, Object> card = pickSettlementCard(channel); |
| | | String providerApplyNo = clean(applyNo); |
| | | String mismatched = providerApplyNo.isBlank() ? "" : applyNoMismatch(providerHuifuId, providerApplyNo); |
| | | |
| | | Map<String, Object> view = new LinkedHashMap<>(); |
| | | view.put("merchantId", String.valueOf(merchant.id())); |
| | |
| | | Map<String, Object> active = activeApplication(merchantId, storeId); |
| | | boolean alreadyManaged = active != null && providerHuifuId.equals(clean(active.get("providerHuifuId"))); |
| | | view.put("alreadyManaged", alreadyManaged); |
| | | // 硬拦截项在这里先告诉运营,免得填完一屏才被拒。 |
| | | // 硬拦截项在这里先告诉运营,免得填完一屏才被拒。申请单号是刚填的那一项,排在第一个。 |
| | | List<String> blockers = new ArrayList<>(); |
| | | if (!mismatched.isBlank()) blockers.add(mismatched); |
| | | if (card == null) blockers.add("通道未返回可用的结算银行卡,无法纳管(分阶段进度与收款闸门都要求先绑定结算卡)"); |
| | | if (active != null && !alreadyManaged) blockers.add(conflictMessage(active)); |
| | | view.put("blockers", blockers); |
| | |
| | | if (!conflict.isBlank()) throw new BusinessException(ErrorCode.CONFLICT, conflict); |
| | | |
| | | Map<String, Object> channel = channelMerchant(providerHuifuId); |
| | | // 商户号核得住不代表申请单号核得住:这两个字段是分开填的,必须配一次对才能建记录。 |
| | | String mismatched = applyNoMismatch(providerHuifuId, providerApplyNo); |
| | | if (!mismatched.isBlank()) throw new BusinessException(ErrorCode.CONFLICT, mismatched); |
| | | Map<String, Object> card = pickSettlementCard(channel); |
| | | if (card == null) throw new BusinessException(ErrorCode.CONFLICT, "通道未返回可用的结算银行卡,无法纳管"); |
| | | String cardNumber = clean(card.get("card_no")); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 把 `(商户号, 申请单号)` 配一次对:**两个字段是一起送给通道的,通道会交叉校验**,所以填错的号 |
| | | * ——包括"真实存在、但属于别的商户号"的号——都会回 `00000001 申请单不存在`(2026-10-12 实测过这两种)。 |
| | | * |
| | | * <p>为什么必须在建记录之前拦:错号**不会误放**(基础审核确认不了、进件永远 pending、闸门一直拒), |
| | | * 但会留下一条收不了款的记录,而刷新给出的却是「汇付查询未成功,请稍后刷新或联系平台运营」—— |
| | | * 那句话看起来像通道抖动,实际是号填错了;更麻烦的是重复纳管是幂等的、不会用新号覆盖旧号,只能解绑重来。 |
| | | * 拦在这里之后,运营当场就知道是哪一栏错了。 |
| | | * |
| | | * <p>返回空串表示配得上;否则返回给运营看的原话。只查询,不下发。 |
| | | */ |
| | | private String applyNoMismatch(String huifuId, String applyNo) { |
| | | Map<String, Object> response; |
| | | try { |
| | | response = providerGateway.queryApplication(huifuId, applyNo); |
| | | } catch (RuntimeException exception) { |
| | | return "申请单号核验失败:通道查询暂时不通,请稍后重试"; |
| | | } |
| | | if (response != null && "00000000".equals(clean(response.get("resp_code")))) return ""; |
| | | String description = response == null ? "" : clean(response.get("resp_desc")); |
| | | return "申请单号与该商户号不匹配(" + (description.isBlank() ? "通道未返回原因" : description) |
| | | + "):两个字段必须属于同一笔进件,请到汇付控台核对后重填"; |
| | | } |
| | | |
| | | /** |
| | | * 重复纳管命中的是上一次留下的那条记录:不新建,但也不能什么都不做。 |
| | | * |
| | | * <p>已通过的记录直接回它(再刷一次只可能把通过翻回待办,没有好处);还在办理中的**再跑一次进度 |