yeg
2026-09-12 d879291f56eb64adffa44f763238ea736b4be99b
talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuPaymentCallbackService.java
@@ -4,9 +4,13 @@
import java.security.MessageDigest;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.alibaba.fastjson.JSON;
import com.talentcard.server.business.DatabaseRows;
import com.talentcard.server.business.payment.HuifuPaymentPayload.TransStatus;
import com.talentcard.server.business.payment.PaymentSettlementService.ProviderPaymentFact;
import com.talentcard.server.business.payment.PaymentSettlementService.SettlementOutcome;
import com.talentcard.server.business.payment.huifu.HuifuSignatureService;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
@@ -14,17 +18,33 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * Handles Huifu asynchronous notifications.
 *
 * <p>The signature is verified first; only then is the payload interpreted. A successful payment
 * notification is handed to {@link PaymentSettlementService}, which compares the request number,
 * merchant number and amount with the order's fixed collecting sub-merchant before any state
 * changes. Duplicated notifications for an already processed request are acknowledged without
 * touching business state.
 */
@Service
public class HuifuPaymentCallbackService {
    private static final String PROVIDER = "HUIFU";
    /** Event states that already describe a final decision for this exact payload. */
    private static final Set<String> TERMINAL_EVENT_STATUSES = Set.of(
            "PROCESSED", "PAYMENT_CLOSED", "UNKNOWN_STATUS",
            "MERCHANT_MISMATCH", "MERCHANT_UNVERIFIED", "AMOUNT_MISMATCH", "COLLECTOR_UNKNOWN");
    private final JdbcTemplate jdbc;
    private final HuifuSignatureService signatureService;
    private final PaymentSettlementService settlementService;
    public HuifuPaymentCallbackService(JdbcTemplate jdbc, HuifuSignatureService signatureService) {
    public HuifuPaymentCallbackService(JdbcTemplate jdbc, HuifuSignatureService signatureService, PaymentSettlementService settlementService) {
        this.jdbc = jdbc;
        this.signatureService = signatureService;
        this.settlementService = settlementService;
    }
    @Transactional
@@ -32,57 +52,51 @@
        if (!signatureService.verifyCallbackPayload(responseData, signature)) {
            throw new BusinessException(ErrorCode.FORBIDDEN, "汇付回调验签失败");
        }
        Map<String, Object> data = parse(responseData);
        String requestNo = text(data.get("req_seq_id"));
        Map<String, Object> envelope = parse(responseData);
        Map<String, Object> data = HuifuPaymentPayload.businessData(envelope);
        String requestNo = HuifuPaymentPayload.firstNonBlank(HuifuPaymentPayload.value(data, "req_seq_id"), HuifuPaymentPayload.value(envelope, "req_seq_id"));
        if (requestNo.isBlank()) throw new BusinessException(ErrorCode.BAD_REQUEST, "汇付回调缺少请求流水号");
        upsertEvent(requestNo, sha256(responseData), "RECEIVED");
        String payloadHash = sha256(responseData);
        boolean alreadyHandled = alreadyHandled(requestNo, payloadHash);
        upsertEvent(requestNo, payloadHash, "RECEIVED");
        if (alreadyHandled) return acknowledgement(requestNo);
        if (handleRefund(requestNo, data)) {
            return acknowledgement(requestNo);
        }
        List<Map<String, Object>> transactions = rows("""
                select id, order_id, status
                from payment_transaction
                where provider = ? and payment_no = ?
                limit 1 for update
                """, PROVIDER, requestNo);
        if (transactions.isEmpty()) {
        String transStatus = HuifuPaymentPayload.value(data, "trans_stat", "transStat");
        if (HuifuPaymentPayload.status(transStatus) == TransStatus.UNKNOWN) {
            updateEvent(requestNo, "UNKNOWN_STATUS");
            return acknowledgement(requestNo);
        }
        if (!transactionExists(requestNo)) {
            updateEvent(requestNo, "UNMATCHED");
            return acknowledgement(requestNo);
        }
        Map<String, Object> transaction = transactions.get(0);
        if (paid(data)) {
            int paymentUpdated = jdbc.update("""
                    update payment_transaction
                    set status = 'SUCCEEDED', provider_transaction_no = ?, provider_response_code = ?,
                        provider_response_message = ?, paid_at = coalesce(paid_at, now())
                    where id = ? and status in ('CREATED', 'CREATE_FAILED', 'PREPAY_CREATED', 'CREATE_UNKNOWN')
                    """, providerTransactionNo(data), resultCode(data), resultMessage(data), transaction.get("id"));
            if (paymentUpdated > 0) {
                jdbc.update("""
                        update benefit_order
                        set order_status = 1,
                            verify_status = case when order_type = 'STORE_CONSUME' then 1 else verify_status end
                        where id = ? and order_status = 0
                        """, transaction.get("orderId"));
                updateEvent(requestNo, "PROCESSED");
            } else if ("SUCCEEDED".equals(String.valueOf(transaction.get("status")))) {
                updateEvent(requestNo, "PROCESSED");
            } else {
                // This is a late callback for a payment that the talent user
                // cancelled before it succeeded.  Keep the order cancelled.
                updateEvent(requestNo, "PAYMENT_CLOSED");
            }
        } else {
        if (!HuifuPaymentPayload.paid(transStatus, HuifuPaymentPayload.code(data))) {
            // The provider did not confirm money: keep the payment pending for the query path.
            jdbc.update("""
                    update payment_transaction
                    set provider_response_code = ?, provider_response_message = ?
                    where id = ? and status in ('CREATED', 'CREATE_FAILED', 'PREPAY_CREATED')
                    """, resultCode(data), resultMessage(data), transaction.get("id"));
                    where provider = ? and payment_no = ? and status in ('CREATED', 'CREATE_FAILED', 'PREPAY_CREATED', 'CREATE_UNKNOWN')
                    """, HuifuPaymentPayload.code(data), HuifuPaymentPayload.message(data), PROVIDER, requestNo);
            updateEvent(requestNo, "PENDING_QUERY");
            return acknowledgement(requestNo);
        }
        SettlementOutcome outcome = settlementService.settle(new ProviderPaymentFact(
                requestNo,
                HuifuPaymentPayload.value(data, "huifu_id", "huifuId"),
                HuifuPaymentPayload.amountInCent(HuifuPaymentPayload.value(data, "trans_amt", "transAmt")),
                providerTransactionNo(data),
                HuifuPaymentPayload.code(data),
                HuifuPaymentPayload.message(data),
                "CALLBACK"));
        updateEvent(requestNo, eventStatus(outcome));
        return acknowledgement(requestNo);
    }
@@ -96,13 +110,15 @@
        if (refunds.isEmpty()) return false;
        Map<String, Object> refund = refunds.get(0);
        if (paid(data)) {
        String transStatus = HuifuPaymentPayload.value(data, "trans_stat", "transStat");
        String code = HuifuPaymentPayload.code(data);
        if (HuifuPaymentPayload.paid(transStatus, code)) {
            jdbc.update("""
                    update order_refund
                    set refund_status = 2, provider = 'HUIFU', provider_refund_no = ?,
                        provider_response_code = ?, provider_response_message = ?, refund_time = coalesce(refund_time, now())
                    where id = ? and refund_status = 1
                    """, providerTransactionNo(data), resultCode(data), resultMessage(data), refund.get("id"));
                    """, providerTransactionNo(data), code, HuifuPaymentPayload.message(data), refund.get("id"));
            jdbc.update("""
                    update benefit_order o
                    set o.order_status = 2
@@ -110,22 +126,53 @@
                      and o.pay_amount <= (select coalesce(sum(r.refund_amount), 0) from order_refund r where r.order_id = o.id and r.refund_status = 2 and r.deleted = 0)
                    """, refund.get("orderId"));
            updateEvent(refundNo, "PROCESSED");
        } else if (terminalFailure(data)) {
        } else if (HuifuPaymentPayload.terminalFailure(transStatus, code)) {
            jdbc.update("""
                    update order_refund
                    set provider_response_code = ?, provider_response_message = ?, refund_status = 3
                    where id = ? and refund_status = 1
                    """, resultCode(data), resultMessage(data), refund.get("id"));
                    """, code, HuifuPaymentPayload.message(data), refund.get("id"));
            updateEvent(refundNo, "REFUND_FAILED");
        } else {
            jdbc.update("""
                    update order_refund
                    set provider_response_code = ?, provider_response_message = ?
                    where id = ? and refund_status = 1
                    """, resultCode(data), resultMessage(data), refund.get("id"));
                    """, code, HuifuPaymentPayload.message(data), refund.get("id"));
            updateEvent(refundNo, "PENDING_QUERY");
        }
        return true;
    }
    private String eventStatus(SettlementOutcome outcome) {
        return switch (outcome) {
            case SETTLED, ALREADY_SETTLED -> "PROCESSED";
            case CLOSED_LATE_SUCCESS -> "PAYMENT_CLOSED";
            case MERCHANT_MISMATCH -> "MERCHANT_MISMATCH";
            case MERCHANT_UNVERIFIED -> "MERCHANT_UNVERIFIED";
            case AMOUNT_MISMATCH -> "AMOUNT_MISMATCH";
            case COLLECTOR_UNKNOWN -> "COLLECTOR_UNKNOWN";
            case UNMATCHED -> "UNMATCHED";
            case NOT_SETTLEABLE -> "PENDING_QUERY";
        };
    }
    private boolean transactionExists(String paymentNo) {
        Long count = jdbc.queryForObject("select count(*) from payment_transaction where provider = ? and payment_no = ?", Long.class, PROVIDER, paymentNo);
        return count != null && count > 0;
    }
    private boolean alreadyHandled(String requestNo, String payloadHash) {
        List<Map<String, Object>> rows = rows("""
                select process_status, payload_sha256
                from payment_callback_event
                where provider = ? and provider_request_no = ?
                limit 1
                """, PROVIDER, requestNo);
        if (rows.isEmpty()) return false;
        Map<String, Object> event = rows.get(0);
        return payloadHash.equals(HuifuPaymentPayload.text(event.get("payloadSha256")))
                && TERMINAL_EVENT_STATUSES.contains(HuifuPaymentPayload.text(event.get("processStatus")));
    }
    private void upsertEvent(String requestNo, String payloadHash, String status) {
@@ -143,35 +190,8 @@
                """, status, PROVIDER, requestNo);
    }
    private boolean paid(Map<String, Object> data) {
        String status = text(data.get("trans_stat"));
        String code = resultCode(data);
        return ("S".equalsIgnoreCase(status) || "SUCCESS".equalsIgnoreCase(status) || "PAID".equalsIgnoreCase(status))
                && ("00000000".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code));
    }
    private boolean terminalFailure(Map<String, Object> data) {
        String status = text(data.get("trans_stat"));
        String code = resultCode(data);
        boolean processing = "P".equalsIgnoreCase(status) || "PROCESSING".equalsIgnoreCase(status) || "PENDING".equalsIgnoreCase(status);
        boolean successCode = "00000000".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code);
        return !processing && (!successCode || "F".equalsIgnoreCase(status) || "FAILED".equalsIgnoreCase(status));
    }
    private String providerTransactionNo(Map<String, Object> data) {
        String value = text(data.get("hf_seq_id"));
        if (value.isBlank()) value = text(data.get("bank_order_no"));
        return value;
    }
    private String resultCode(Map<String, Object> data) {
        String code = text(data.get("sub_resp_code"));
        return code.isBlank() ? text(data.get("resp_code")) : code;
    }
    private String resultMessage(Map<String, Object> data) {
        String message = text(data.get("sub_resp_desc"));
        return message.isBlank() ? text(data.get("resp_desc")) : message;
        return HuifuPaymentPayload.value(data, "hf_seq_id", "bank_order_no", "hfSeqId");
    }
    private String acknowledgement(String requestNo) {
@@ -196,10 +216,6 @@
        } catch (Exception exception) {
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, "支付回调摘要失败");
        }
    }
    private String text(Object value) {
        return value == null ? "" : String.valueOf(value).trim();
    }
    private List<Map<String, Object>> rows(String sql, Object... args) {