| | |
| | | /** Six independent evidence-backed stages; provider calls never hold application row locks. */ |
| | | @Service |
| | | public class MerchantOnboardingProgressService { |
| | | /** 分阶段进度的灰度门槛;03 票把它换成通用规则时一并删除。 */ |
| | | public static final String RECOVERY_HUIFU_ID = "6666000241335656"; |
| | | private static final Map<String,String> LABELS = new LinkedHashMap<>(); |
| | | static { |
| | |
| | | private final HuifuOnboardingProgressGateway gateway; |
| | | public MerchantOnboardingProgressService(JdbcTemplate jdbc, HuifuOnboardingProgressGateway gateway) { this.jdbc=jdbc; this.gateway=gateway; } |
| | | |
| | | /** |
| | | * 分阶段进度当前只对指定存量商户放行。 |
| | | * |
| | | * <p><b>这仍是一道灰度门槛,不是通用规则。</b>把判断换成「有结算卡且未被拒绝」会同时给所有商户 |
| | | * 切换行为:进件失败不再释放银行卡(`MerchantOnboardingRetryReleaseTests`)、回调改走分阶段刷新 |
| | | * (`MerchantSettlementCallbackIdempotencyTests`),共 5 个测试会红。那是策略变更,属于 03 票 |
| | | * 「分阶段手动推进 + 局部失败保留原商户号和银行卡」,不在 04 的清理范围内。 |
| | | */ |
| | | public boolean supports(Map<String,Object> application) { return RECOVERY_HUIFU_ID.equals(text(application.get("providerHuifuId"))); } |
| | | |
| | | public Map<String,Object> view(Long id) { |
| | | Map<String,Object> application = application(id); |
| | | if (!supports(application)) throw new BusinessException(ErrorCode.CONFLICT,"This recovery is limited to the designated existing merchant"); |
| | | return view(application); |
| | | } |
| | | public Map<String,Object> view(Long id) { return view(application(id)); } |
| | | |
| | | @org.springframework.transaction.annotation.Transactional(propagation = org.springframework.transaction.annotation.Propagation.NOT_SUPPORTED) |
| | | public Map<String,Object> refresh(Long applicationId, boolean retry) { |
| | | Map<String,Object> application=application(applicationId); |
| | | if (!supports(application)) throw new BusinessException(ErrorCode.CONFLICT,"This recovery is limited to the designated existing merchant"); |
| | | jdbc.update("insert ignore into merchant_onboarding_progress(application_id,stages_json) values (?, '{}')", applicationId); |
| | | String owner=UUID.randomUUID().toString(); |
| | | if(jdbc.update("update merchant_onboarding_progress set sync_owner=?,sync_until=date_add(now(),interval 5 minute) where application_id=? and (sync_owner is null or sync_until<now())", owner, applicationId)==0) return view(application); |
| | |
| | | if ("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,owner,stages,retry); |
| | | advanceDelay(applicationId,text(application.get("providerHuifuId")),owner,stages,retry); |
| | | } |
| | | jdbc.update("update merchant_onboarding_progress set stages_json=? where application_id=? and sync_owner=?",JSON.toJSONString(stages),applicationId,owner); |
| | | jdbc.update(""" |
| | |
| | | && LABELS.keySet().stream().allMatch(key -> "confirmed".equals(stages.get(key))); |
| | | } |
| | | |
| | | private void advanceDelay(Long id, String owner, Map<String,Object> stages, boolean retry) { |
| | | private void advanceDelay(Long id, String huifuId, String owner, Map<String,Object> stages, boolean retry) { |
| | | List<Map<String,Object>> attempts=rows("select * from merchant_onboarding_attempt where application_id=? and step='delay' order by id desc limit 1",id); |
| | | Map<String,Object> attempt=attempts.isEmpty() ? null : attempts.get(0); |
| | | if ("confirmed".equals(stages.get("delay"))) { |
| | |
| | | if (attempt != null && !"failed".equals(text(attempt.get("status")))) { |
| | | String applyNo=text(attempt.get("applyNo")); |
| | | if (!applyNo.isBlank() && !List.of("succeeded","confirmed").contains(text(attempt.get("status")))) { |
| | | Map<String,Object> result=gateway.queryApplication(RECOVERY_HUIFU_ID,applyNo); |
| | | Map<String,Object> result=gateway.queryApplication(huifuId,applyNo); |
| | | if (result != null && "00000000".equals(text(result.get("resp_code"))) && List.of("N","F").contains(text(result.get("apply_status")))) { |
| | | jdbc.update("update merchant_onboarding_attempt set status='failed',response_code=? where id=? and status<>'confirmed'",text(result.get("resp_code")),attempt.get("id")); |
| | | attempt.put("status","failed"); |
| | |
| | | if (inserted==0) return; |
| | | stages.put("delay","pending"); |
| | | try { |
| | | Map<String,Object> result=gateway.configureDelay(RECOVERY_HUIFU_ID,requestNo,date); |
| | | Map<String,Object> result=gateway.configureDelay(huifuId,requestNo,date); |
| | | String code=result==null ? "" : text(result.get("resp_code")); |
| | | String status=code.isBlank() ? "unknown" : List.of("00000000","00000100","90000000").contains(code) ? "pending" : "failed"; |
| | | jdbc.update("update merchant_onboarding_attempt set status=?,apply_no=nullif(?,''),response_code=? where request_no=?",status,result==null ? "" : text(result.get("apply_no")),code,requestNo); |
| | |
| | | result.put("stages",progress); result.put("reason","请刷新进度,核实原申请与各项能力"); |
| | | result.put("retryable", "failed".equals(stages.get("delay"))); |
| | | result.put("status",complete(stages) ? "approved" : "pending"); |
| | | result.put("reason",complete(stages) ? "收款所需能力已核实,沿用原商户号和结算银行卡" : "请刷新进度核实各项能力;微信行业规则仍待平台确认,暂不提交微信补开"); |
| | | result.put("reason",complete(stages) ? "收款能力已核实,沿用原商户号和结算银行卡" : "请刷新进度核实各项能力,并按待办理项补齐缺失能力"); |
| | | return result; |
| | | } |
| | | |
| | | /** Called only after the existing HTTP callback boundary has verified Huifu's signature. */ |
| | | public Long acceptDelayCallback(Map<String,Object> data) { |
| | | // 用回调自带的 huifu_id 去匹配它自己的申请单,不再按固定商户号过滤。 |
| | | String huifuId=text(data.get("huifu_id")); |
| | | if (huifuId.isBlank()) return null; |
| | | List<Map<String,Object>> matches=rows(""" |
| | | select t.id,t.application_id from merchant_onboarding_attempt t |
| | | join merchant_onboarding_application a on a.id=t.application_id |
| | | where t.step='delay' and t.request_no=? and t.request_date=? and a.provider_huifu_id=? |
| | | """,text(data.get("req_seq_id")),text(data.get("req_date")),RECOVERY_HUIFU_ID); |
| | | if (matches.isEmpty() || !RECOVERY_HUIFU_ID.equals(text(data.get("huifu_id")))) return null; |
| | | """,text(data.get("req_seq_id")),text(data.get("req_date")),huifuId); |
| | | if (matches.isEmpty()) return null; |
| | | Map<String,Object> attempt=matches.get(0); |
| | | String audit=text(data.get("audit_status")); |
| | | String state="Y".equals(audit) ? "succeeded" : List.of("N","F","C").contains(audit) ? "failed" : "pending"; |