shinnosuke
2026-09-10 65b57875700bf42ebd17eafa01a265e5690ce271
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingGateway.java
@@ -8,46 +8,88 @@
import com.talentcard.server.common.ErrorCode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class HuifuIndividualOnboardingGateway {
    private static final Object SDK_LOCK = new Object();
    private static final Logger log = LoggerFactory.getLogger(HuifuIndividualOnboardingGateway.class);
    private final HuifuPaymentProperties properties;
    public HuifuIndividualOnboardingGateway(HuifuPaymentProperties properties) { this.properties = properties; }
    public Result create(HuifuIndividualOnboardingCommand command) {
        ensureConfigured();
        Map<String, Object> request = new java.util.LinkedHashMap<>();
        Map<String, Object> card = new LinkedHashMap<>();
        card.put("card_type", "1");
        card.put("card_name", command.name());
        card.put("card_no", command.cardNumber());
        card.put("prov_id", command.bankProvinceCode());
        card.put("area_id", command.bankCityCode());
        card.put("bank_code", command.bankCode());
        card.put("cert_type", "00");
        card.put("cert_no", command.idCardNo());
        card.put("cert_validity_type", command.certificateValidityType());
        card.put("cert_begin_date", command.certificateBeginDate());
        if (!blank(command.certificateEndDate())) card.put("cert_end_date", command.certificateEndDate());
        card.put("mp", command.mobileNo());
        card.put("is_settle_default", "Y");
        Map<String, Object> cash = new LinkedHashMap<>();
        cash.put("cash_type", "DM");
        cash.put("fix_amt", "0.00");
        cash.put("fee_rate", "");
        Map<String, Object> request = new LinkedHashMap<>();
        request.put("req_seq_id", command.applicationNo());
        request.put("req_date", LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE));
        request.put("name", command.name());
        request.put("cert_type", "00");
        request.put("cert_no", command.idCardNo());
        request.put("cert_validity_type", command.certificateValidityType());
        request.put("cert_begin_date", command.certificateBeginDate());
        if (!blank(command.certificateEndDate())) request.put("cert_end_date", command.certificateEndDate());
        request.put("mobile_no", command.mobileNo());
        if (!blank(command.address())) request.put("address", command.address());
        request.put("upper_huifu_id", properties.getOnboardingUpperHuifuId().trim());
        request.put("reg_name", command.name());
        request.put("short_name", command.name());
        request.put("mcc", command.mcc());
        request.put("scene_type", "ONLINE");
        request.put("prov_id", command.provinceCode());
        request.put("area_id", command.cityCode());
        request.put("district_id", command.districtCode());
        request.put("detail_addr", command.address());
        request.put("legal_cert_no", command.idCardNo());
        request.put("legal_cert_validity_type", command.certificateValidityType());
        request.put("legal_cert_begin_date", command.certificateBeginDate());
        putIfPresent(request, "legal_cert_end_date", command.certificateEndDate());
        request.put("legal_addr", command.address());
        request.put("legal_cert_front_pic", command.legalFrontPictureId());
        request.put("legal_cert_back_pic", command.legalBackPictureId());
        request.put("contact_name", command.name());
        request.put("contact_mobile_no", command.mobileNo());
        request.put("contact_email", command.email());
        request.put("login_name", loginName(command.applicationNo()));
        request.put("card_info", JSON.toJSONString(card));
        request.put("settle_card_front_pic", command.settlementCardFrontPictureId());
        request.put("cash_config", JSON.toJSONString(List.of(cash)));
        putIfPresent(request, "async_return_url", properties.getMerchantOnboardingNotifyUrl());
        try {
            Map<String, Object> response;
            synchronized (SDK_LOCK) {
                initializeSdk();
                response = BasePayClient.request(request, "v2.user.basicdata.indv", false);
                Map<String, Object> response = BasePayClient.request(request, "v2.merchant.basicdata.indv", false);
                log.info("Huifu individual merchant onboarding response: applicationNo={}, upperHuifuId={}, response={}", command.applicationNo(), properties.getOnboardingUpperHuifuId(), JSON.toJSONString(response));
                return Result.from(response);
            }
            return Result.from(response);
        } catch (BusinessException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new HuifuPaymentGatewayException("Huifu individual onboarding request failed", outcomeUnknown(exception), exception);
            log.error("Huifu individual merchant onboarding request error: applicationNo={}, upperHuifuId={}", command.applicationNo(), properties.getOnboardingUpperHuifuId(), exception);
            throw new HuifuPaymentGatewayException("Huifu individual merchant onboarding request failed", outcomeUnknown(exception), exception);
        }
    }
    private void ensureConfigured() {
        if (!properties.isEnabled()) throw new BusinessException(ErrorCode.CONFLICT, "Huifu payment is disabled");
        if (blank(properties.getSystemId()) || blank(properties.getProductId()) || blank(properties.getPrivateKey()) || blank(properties.getPlatformPublicKey())) throw new BusinessException(ErrorCode.CONFLICT, "Huifu onboarding configuration is incomplete");
        if (blank(properties.getSystemId()) || blank(properties.getProductId()) || blank(properties.getOnboardingUpperHuifuId()) || blank(properties.getPrivateKey()) || blank(properties.getPlatformPublicKey())) throw new BusinessException(ErrorCode.CONFLICT, "Huifu merchant onboarding configuration is incomplete");
    }
    private void initializeSdk() throws Exception {
@@ -61,20 +103,20 @@
        BasePay.initWithMerConfig(config);
    }
    private boolean blank(String value) { return value == null || value.isBlank(); }
    private static String loginName(String applicationNo) { return ("tc" + applicationNo).replaceAll("[^0-9A-Za-z_]", ""); }
    private static void putIfPresent(Map<String, Object> request, String key, String value) { if (!blank(value)) request.put(key, value.trim()); }
    private static boolean blank(String value) { return value == null || value.isBlank(); }
    private boolean outcomeUnknown(Exception exception) { String message = exception.getMessage() == null ? "" : exception.getMessage().toLowerCase(); return message.contains("timeout") || message.contains("timed out") || message.contains("no response"); }
    @SuppressWarnings("unchecked")
    public record Result(boolean accepted, String huifuId, String code, String message) {
    public record Result(boolean accepted, String huifuId, String applyNo, String tokenNo, String code, String message) {
        static Result from(Map<String, Object> response) {
            Object raw = response.get("data");
            Map<String, Object> data = raw instanceof Map<?, ?> value ? (Map<String, Object>) value : raw instanceof String json && !json.isBlank() ? JSON.parseObject(json, Map.class) : response;
            String code = value(data, "sub_resp_code", "resp_code", "code");
            String message = value(data, "sub_resp_desc", "resp_desc", "message");
            String huifuId = value(data, "huifu_id", "huifuId");
            return new Result("00000000".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code), huifuId, code, message);
            return new Result(success(code), value(data, "huifu_id", "huifuId"), value(data, "apply_no", "applyNo"), value(data, "token_no", "tokenNo"), code, value(data, "sub_resp_desc", "resp_desc", "message"));
        }
        private static boolean success(String code) { return "00000000".equals(code) || "00000100".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code); }
        private static String value(Map<String, Object> data, String... keys) { for (String key : keys) if (data.get(key) != null) return String.valueOf(data.get(key)).trim(); return ""; }
    }
}