yeg
2026-09-12 6af5b0ccfcfdee62c53d2edcac97c92ede876692
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package com.talentcard.server.business.payment.huifu;
 
import com.alibaba.fastjson.JSON;
import com.huifu.bspay.sdk.opps.client.BasePayClient;
import com.huifu.bspay.sdk.opps.core.BasePay;
import com.huifu.bspay.sdk.opps.core.config.MerConfig;
import com.huifu.bspay.sdk.opps.core.request.V3TradePaymentScanpayRefundRequest;
import com.huifu.bspay.sdk.opps.core.request.V3TradePaymentScanpayRefundqueryRequest;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
 
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
 
@Service
public class HuifuRefundGateway {
 
    private static final Object SDK_INIT_LOCK = new Object();
    private static final DateTimeFormatter REQUEST_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
    private static final Logger log = LoggerFactory.getLogger(HuifuRefundGateway.class);
 
    private final HuifuPaymentProperties properties;
    private volatile boolean initialized;
 
    public HuifuRefundGateway(HuifuPaymentProperties properties) {
        this.properties = properties;
    }
 
    public Map<String, Object> createRefund(HuifuRefundCommand command) {
        ensureConfigured();
        initializeSdk();
        V3TradePaymentScanpayRefundRequest request = new V3TradePaymentScanpayRefundRequest();
        request.setReqDate(REQUEST_DATE.format(LocalDateTime.now()));
        request.setReqSeqId(command.refundNo());
        request.setHuifuId(properties.getMerchantId().trim());
        request.setOrdAmt(BigDecimal.valueOf(command.amountInCent(), 2).toPlainString());
        request.setOrgReqDate(command.originalRequestDate());
 
        Map<String, Object> extendInfo = new LinkedHashMap<>();
        if (!blank(command.originalProviderNo())) extendInfo.put("org_hf_seq_id", command.originalProviderNo());
        if (!blank(command.paymentNo())) extendInfo.put("org_req_seq_id", command.paymentNo());
        if (!blank(properties.getNotifyUrl())) extendInfo.put("notify_url", properties.getNotifyUrl().trim());
        request.setExtendInfo(extendInfo);
        return execute(request, "Huifu refund request failed");
    }
 
    public Map<String, Object> queryRefund(HuifuRefundCommand command, String providerRefundNo) {
        ensureConfigured();
        initializeSdk();
        V3TradePaymentScanpayRefundqueryRequest request = new V3TradePaymentScanpayRefundqueryRequest();
        request.setHuifuId(properties.getMerchantId().trim());
        request.setOrgReqDate(command.originalRequestDate());
        Map<String, Object> extendInfo = new LinkedHashMap<>();
        if (!blank(providerRefundNo)) extendInfo.put("org_hf_seq_id", providerRefundNo);
        if (!blank(command.refundNo())) extendInfo.put("org_req_seq_id", command.refundNo());
        request.setExtendInfo(extendInfo);
        return execute(request, "Huifu refund query failed");
    }
 
    private Map<String, Object> execute(Object request, String operation) {
        try {
            if (request instanceof V3TradePaymentScanpayRefundRequest refundRequest) {
                return BasePayClient.request(refundRequest, false);
            }
            return BasePayClient.request((V3TradePaymentScanpayRefundqueryRequest) request, false);
        } catch (Exception exception) {
            log.warn("{}: type={}, message={}", operation, exception.getClass().getSimpleName(), safeMessage(exception.getMessage()));
            throw new HuifuPaymentGatewayException("Huifu payment operation failed", isOutcomeUnknown(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.getMerchantId())
                || blank(properties.getPrivateKey()) || blank(properties.getPlatformPublicKey())) {
            throw new BusinessException(ErrorCode.CONFLICT, "Huifu payment configuration is incomplete");
        }
    }
 
    private void initializeSdk() {
        if (initialized) return;
        synchronized (SDK_INIT_LOCK) {
            if (initialized) return;
            BasePay.debug = false;
            BasePay.prodMode = "prod".equalsIgnoreCase(properties.getEnvironment()) ? BasePay.MODE_PROD : BasePay.MODE_TEST;
            MerConfig config = new MerConfig();
            config.setSysId(properties.getSystemId().trim());
            config.setProductId(properties.getProductId().trim());
            config.setRsaPrivateKey(properties.getPrivateKey().trim());
            config.setRsaPublicKey(properties.getPlatformPublicKey().trim());
            try {
                BasePay.initWithMerConfig(config);
                initialized = true;
            } catch (Exception exception) {
                throw new BusinessException(ErrorCode.CONFLICT, "Huifu payment SDK initialization failed");
            }
        }
    }
 
    private boolean blank(String value) { return value == null || value.isBlank(); }
 
    private String safeMessage(String message) {
        if (message == null || message.isBlank()) return "";
        return message.length() > 500 ? message.substring(0, 500) : message;
    }
 
    private boolean isOutcomeUnknown(Exception exception) {
        String message = safeMessage(exception.getMessage()).toLowerCase();
        return message.contains("no response") || message.contains("timeout") || message.contains("timed out");
    }
}