| | |
| | | // 放在提交时校验:这两张图是拿到微信收款能力的硬条件,缺了也能把进件提上去, |
| | | // 但后面微信开通和实名一定被通道驳回,那时商户得把整份进件重走一遍。 |
| | | if (blank(request.storeHeaderUrl()) || blank(request.storeIndoorUrl())) throw new BusinessException(ErrorCode.BAD_REQUEST, "请上传店铺照片与店内环境照,微信支付开通与实名认证都需要"); |
| | | // 经营详细地址(进件的 detail_addr)。线下按「有实体经营场所」口径报备后它是通道必填项, |
| | | // 而证件地址(legal_addr)语义不同、不能顶替——两者原先共用同一个值,这里必须分开采集。 |
| | | if (blank(request.businessAddress())) throw new BusinessException(ErrorCode.BAD_REQUEST, "请填写经营详细地址"); |
| | | } |
| | | boolean privateSettlement = isPrivateSettlement(request.subjectType(), request.companyType()); |
| | | if (privateSettlement && blank(request.settlementCardFrontUrl())) throw new BusinessException(ErrorCode.BAD_REQUEST, "请上传结算银行卡卡号面"); |
| | |
| | | String no = BusinessIdGenerator.next("MO"); |
| | | String certificateValidityType = certificateValidityType(request.subjectCertificateValidityType()); |
| | | String certificateBeginDate = certificateBeginDate(request.subjectCertificateBeginDate()); |
| | | jdbc.update("insert into merchant_onboarding_application(merchant_id, store_id, application_no, subject_type, subject_name, credit_code, license_validity_type, license_begin_date, license_end_date, subject_id_no_masked, subject_id_no_encrypted, subject_cert_validity_type, subject_cert_begin_date, subject_cert_end_date, registered_region, registered_province_code, registered_city_code, registered_district_code, registered_address, merchant_name, merchant_short_name, company_type, business_license_url, established_date, registered_capital, legal_id_front_url, legal_id_back_url, settlement_card_front_url, legal_certificate_address, legal_mobile, administrator_email, contact_name, contact_mobile, store_header_url, store_indoor_url, idempotency_key) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", principal.merchantId(), principal.storeId(), no, request.subjectType(), request.subjectName().trim(), clean(request.creditCode()), certificateValidityType(request.licenseValidityType()), certificateBeginDate(request.licenseBeginDate()), certificateEndDate(request.licenseEndDate()), maskId(request.subjectIdNumber()), sensitiveDataCrypto.encrypt(clean(request.subjectIdNumber()).toUpperCase()), certificateValidityType, certificateBeginDate, certificateEndDate(request.subjectCertificateEndDate()), clean(request.registeredRegion()), clean(request.registeredProvinceCode()), clean(request.registeredCityCode()), clean(request.registeredDistrictCode()), clean(request.registeredAddress()), clean(request.merchantName()), clean(request.merchantShortName()), clean(request.companyType()), clean(request.businessLicenseUrl()), clean(request.establishedDate()), clean(request.registeredCapital()), clean(request.legalIdFrontUrl()), clean(request.legalIdBackUrl()), clean(settlementProofUrl), clean(request.legalCertificateAddress()), clean(request.legalMobile()), clean(request.administratorEmail()), request.contactName().trim(), request.contactMobile().trim(), clean(request.storeHeaderUrl()), clean(request.storeIndoorUrl()), key.trim()); |
| | | jdbc.update("insert into merchant_onboarding_application(merchant_id, store_id, application_no, subject_type, subject_name, credit_code, license_validity_type, license_begin_date, license_end_date, subject_id_no_masked, subject_id_no_encrypted, subject_cert_validity_type, subject_cert_begin_date, subject_cert_end_date, registered_region, registered_province_code, registered_city_code, registered_district_code, registered_address, merchant_name, merchant_short_name, company_type, business_license_url, established_date, registered_capital, legal_id_front_url, legal_id_back_url, settlement_card_front_url, legal_certificate_address, legal_mobile, administrator_email, contact_name, contact_mobile, store_header_url, store_indoor_url, business_address, idempotency_key) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", principal.merchantId(), principal.storeId(), no, request.subjectType(), request.subjectName().trim(), clean(request.creditCode()), certificateValidityType(request.licenseValidityType()), certificateBeginDate(request.licenseBeginDate()), certificateEndDate(request.licenseEndDate()), maskId(request.subjectIdNumber()), sensitiveDataCrypto.encrypt(clean(request.subjectIdNumber()).toUpperCase()), certificateValidityType, certificateBeginDate, certificateEndDate(request.subjectCertificateEndDate()), clean(request.registeredRegion()), clean(request.registeredProvinceCode()), clean(request.registeredCityCode()), clean(request.registeredDistrictCode()), clean(request.registeredAddress()), clean(request.merchantName()), clean(request.merchantShortName()), clean(request.companyType()), clean(request.businessLicenseUrl()), clean(request.establishedDate()), clean(request.registeredCapital()), clean(request.legalIdFrontUrl()), clean(request.legalIdBackUrl()), clean(settlementProofUrl), clean(request.legalCertificateAddress()), clean(request.legalMobile()), clean(request.administratorEmail()), request.contactName().trim(), request.contactMobile().trim(), clean(request.storeHeaderUrl()), clean(request.storeIndoorUrl()), clean(request.businessAddress()), key.trim()); |
| | | String cardNumber = request.bankCardNumber().trim(); |
| | | String cardKey = key.trim() + ":initial"; |
| | | if (cardKey.length() > 128) cardKey = cardKey.substring(0, 128); |
| | |
| | | HuifuIndividualOnboardingGateway.Result result = individualOnboardingGateway.create(new HuifuIndividualOnboardingCommand( |
| | | applicationNo, request.subjectName().trim(), request.subjectIdNumber().trim().toUpperCase(), certificateValidityType(request.subjectCertificateValidityType()), certificateBeginDate(request.subjectCertificateBeginDate()), certificateEndDate(request.subjectCertificateEndDate()), request.contactMobile().trim(), clean(request.registeredAddress()), request.administratorEmail().trim(), |
| | | mcc(principal.merchantId(), "individual", request.companyType()), clean(request.registeredProvinceCode()), clean(request.registeredCityCode()), clean(request.registeredDistrictCode()), legalFrontPictureId, legalBackPictureId, |
| | | request.bankCardNumber().trim(), request.bankProvinceCode().trim(), request.bankCityCode().trim(), clean(branch.get("headBankCode")), settlementCardPictureId, storeHeaderPictureId)); |
| | | request.bankCardNumber().trim(), request.bankProvinceCode().trim(), request.bankCityCode().trim(), clean(branch.get("headBankCode")), settlementCardPictureId, storeHeaderPictureId, clean(request.businessAddress()))); |
| | | applyOnboardingResult(applicationNo, bankCardId, result.accepted(), result.huifuId(), result.applyNo(), result.tokenNo(), result.code(), result.message()); |
| | | } catch (HuifuPaymentGatewayException exception) { |
| | | jdbc.update("update merchant_onboarding_application set reject_reason=? where application_no=? and status='pending'", "Huifu submission outcome is pending confirmation", applicationNo); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 微信小程序支付配置是否已经在通道生效:人才端 AppID 是否绑定在 {@code fee_type=01} 的配置项下。 |
| | | * 微信小程序支付配置是否已经在通道生效:人才端 AppID 是否绑定在配置的业务开通类型 |
| | | * (线下 {@code 02}、线上 {@code 01},取 {@code merchant-wechat-fee-type})下的配置项里。 |
| | | * 与分阶段进度用的是同一份证据。查询失败按「未生效」处理——宁可多提一次也不要漏配。 |
| | | */ |
| | | private boolean wechatConfigEffective(String huifuId) { |
| | |
| | | Map<String, Object> config = onboardingQueryGateway.queryWechatConfig(huifuId); |
| | | if (config == null || !"00000000".equals(clean(config.get("resp_code")))) return false; |
| | | String appId = huifuPaymentProperties.getMiniProgramAppId().trim(); |
| | | String feeType = huifuPaymentProperties.getMerchantWechatFeeType(); |
| | | for (Map<String, Object> entry : providerList(config.get("atu_config_bo_list"))) { |
| | | if (!"01".equals(clean(entry.get("fee_type")))) continue; |
| | | if (!feeType.equals(clean(entry.get("fee_type")))) continue; |
| | | for (Map<String, Object> item : providerList(entry.get("wx_config_list"))) { |
| | | if ("1".equals(clean(item.get("config_type"))) && appId.equals(clean(item.get("config_value")))) return true; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 微信业务是否已经在通道开通:商户详情的 {@code qry_wx_conf_list} 里出现线上三场景之一即视为已开通。 |
| | | * 微信业务是否已经在通道开通:商户详情的 {@code qry_wx_conf_list} 里出现配置的支付场景之一 |
| | | * (线下 {@code 1/2/3}、线上 {@code 4/5/12},取 {@code merchant-wechat-scenes})即视为已开通。 |
| | | * 业务开通是申请型接口,重复提交会给同一商户开出第二份申请,所以提交前必须先查。 |
| | | */ |
| | | private boolean wechatBusinessEffective(String huifuId) { |
| | | try { |
| | | Map<String, Object> detail = onboardingQueryGateway.queryCapabilities(huifuId); |
| | | if (detail == null || !"00000000".equals(clean(detail.get("resp_code")))) return false; |
| | | List<String> scenes = huifuPaymentProperties.merchantWechatSceneList(); |
| | | return providerList(detail.get("qry_wx_conf_list")).stream() |
| | | .anyMatch(item -> List.of("4", "5", "12").contains(clean(item.get("pay_scene")))); |
| | | .anyMatch(item -> scenes.contains(clean(item.get("pay_scene")))); |
| | | } catch (RuntimeException exception) { |
| | | log.warn("Huifu merchant business open probe failed: huifuId={}", huifuId, exception); |
| | | return false; |