| | |
| | | @Service |
| | | public class MerchantSettlementService { |
| | | private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MerchantSettlementService.class); |
| | | /** 商家档案给不出可识别经营类型时的兜底 MCC(「其它」)。 */ |
| | | private static final String FALLBACK_MCC = "7399"; |
| | | private static final Map<String, String> MCC_BY_MERCHANT_TYPE = Map.ofEntries( |
| | | Map.entry("food", "5812"), Map.entry("scenic", "7991"), Map.entry("hotel", "7011"), Map.entry("apartment", "7011"), |
| | | Map.entry("cinema", "7832"), Map.entry("fuel", "5541"), Map.entry("parking", "7523"), Map.entry("carWash", "7542"), |
| | |
| | | /** |
| | | * 进件主体所属行业的 MCC。 |
| | | * |
| | | * <p>个人(小微)主体在进件表单第一步单独选过「行业类型」(餐饮服务/酒店服务/景区服务…), |
| | | * 它跟着企业主体的公司类型一起落在 {@code company_type} 列上。原先个人进件的 MCC 只认商家档案上的 |
| | | * 经营类型,等于把商户自己在进件时选的行业**静默丢掉**:档案没配好就一律落 7399(其它), |
| | | * 通道侧的微信行业类目和费率跟着一起错。所以个人主体优先用进件时选的行业, |
| | | * 行业识别不出来(选了「其它」)再退回商家档案。 |
| | | * <p><b>行业以系统里的商家档案为准</b>(2026-09-16 业务口径:「行业改不了,只有我们系统动态地去匹配 |
| | | * 行业」)。商家档案上的经营类型是我们自己维护的,也是匹配微信行业类目与费率的依据, |
| | | * 不该被进件表单上的一次选择盖掉;已进件商户也不提供补改行业的入口。 |
| | | * |
| | | * <p>商户在个人进件表单第一步选的「行业类型」只在**档案给不出具体行业时**兜底 |
| | | * (档案未配、或配成「其它」未识别 → {@link #FALLBACK_MCC}),避免档案缺数据时一律落「其它」。 |
| | | * |
| | | * <p>{@code mcc} 是进件的必填项;业务开通只在没有 {@code fee_rule_id} 时才用它兜底。 |
| | | */ |
| | | private String mcc(Long merchantId, String subjectType, String industryType) { |
| | | if ("individual".equalsIgnoreCase(clean(subjectType))) { |
| | | String industry = mccForIndustry(industryType); |
| | | if (!industry.isBlank()) return industry; |
| | | } |
| | | return mcc(merchantId); |
| | | String configured = mcc(merchantId); |
| | | // 个体工商户走企业进件接口,表单上没有「行业类型」这一项,只有个人主体才有申报值。 |
| | | if (!"individual".equalsIgnoreCase(clean(subjectType))) return configured; |
| | | return preferConfiguredMcc(configured, industryType); |
| | | } |
| | | |
| | | /** |
| | | * 系统档案优先、商户申报兜底。取值为静态形式,便于直接断言这条规则本身 |
| | | * (见 {@code MerchantOnboardingIndustryMccTests})。 |
| | | */ |
| | | static String preferConfiguredMcc(String configuredMcc, String declaredIndustry) { |
| | | if (!FALLBACK_MCC.equals(cleanStatus(configuredMcc))) return configuredMcc == null ? "" : configuredMcc.trim(); |
| | | String declared = mccForIndustry(declaredIndustry); |
| | | return declared.isBlank() ? FALLBACK_MCC : declared; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | static String mccForIndustry(String industryType) { |
| | | String industry = MerchantTypeCatalog.canonicalCode(cleanStatus(industryType)); |
| | | return "other".equals(industry) ? "" : MCC_BY_MERCHANT_TYPE.getOrDefault(industry, "7399"); |
| | | return "other".equals(industry) ? "" : MCC_BY_MERCHANT_TYPE.getOrDefault(industry, FALLBACK_MCC); |
| | | } |
| | | |
| | | private String mcc(Long merchantId) { |
| | |
| | | if (merchants.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "商家不存在"); |
| | | String code = clean(merchants.get(0).get("merchantTypeCode")); |
| | | if (code.isBlank()) code = MerchantTypeCatalog.canonicalCode(clean(merchants.get(0).get("merchantType"))); |
| | | return MCC_BY_MERCHANT_TYPE.getOrDefault(code, "7399"); |
| | | return MCC_BY_MERCHANT_TYPE.getOrDefault(code, FALLBACK_MCC); |
| | | } |
| | | private String normalizeRegisteredCapital(String companyType, String value) { |
| | | String text = clean(value); boolean required = List.of("国营企业", "私营企业", "外资企业", "其它组织", "事业单位").contains(clean(companyType)); |