yeg
2026-09-16 5c33cf1f2622d4470b861cb041024ead1775c828
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuDelayAccountGateway.java
@@ -24,6 +24,9 @@
 *
 * <p>业务上是否延迟由订单类型决定:先付钱后核销的订单延迟,核销成功才确认;支付即核销的订单不延迟,
 * 因此不需要确认。
 *
 * <p>核销被撤销时走反方向:交易确认退款把已确认给商户的钱退回延迟户,重新变成不可结算、不可取现,
 * 等商户重新核销时再确认一次。这条路径的结果分类与确认分开,见 {@link HuifuDelayRevert}。
 */
@Service
public class HuifuDelayAccountGateway {
@@ -81,8 +84,8 @@
        return execute(request, CONFIRM_QUERY_FUNCTION, "汇付交易确认查询失败");
    }
    /** 把已确认的金额退回原商户账户。 */
    public HuifuDelayConfirmation refund(HuifuDelayConfirmRefundCommand command) {
    /** 把已确认的金额退回原商户账户(延迟户),撤销核销用它把钱要回来。 */
    public HuifuDelayRevert refund(HuifuDelayConfirmRefundCommand command) {
        ensureConfigured();
        initializeSdk();
        Map<String, Object> request = new LinkedHashMap<>();
@@ -96,10 +99,10 @@
                "req_seq_id", command.refundNo(),
                "huifu_id", command.huifuId().trim(),
                "org_req_seq_id", command.confirmNo())));
        return execute(request, CONFIRM_REFUND_FUNCTION, "汇付交易确认退款失败");
        return executeRevert(request, CONFIRM_REFUND_FUNCTION, "汇付交易确认退款失败");
    }
    private HuifuDelayConfirmation execute(Map<String, Object> request, String function, String failureMessage) {
    private Map<String, Object> call(Map<String, Object> request, String function, String failureMessage) {
        Map<String, Object> response;
        try {
            response = BasePayClient.request(request, function, false);
@@ -109,7 +112,15 @@
            throw new HuifuPaymentGatewayException(failureMessage, isOutcomeUnknown(exception), exception);
        }
        if (response == null) throw new HuifuPaymentGatewayException(failureMessage, true, null);
        return HuifuDelayConfirmation.from(response);
        return response;
    }
    private HuifuDelayConfirmation execute(Map<String, Object> request, String function, String failureMessage) {
        return HuifuDelayConfirmation.from(call(request, function, failureMessage));
    }
    private HuifuDelayRevert executeRevert(Map<String, Object> request, String function, String failureMessage) {
        return HuifuDelayRevert.from(call(request, function, failureMessage));
    }
    private void ensureConfigured() {
@@ -239,4 +250,65 @@
            return HuifuPayload.amountInCent(confirmedAmount);
        }
    }
    /**
     * 交易确认退款(撤销核销回退资金)的结果。
     *
     * <p>刻意不复用 {@link HuifuDelayConfirmation} 的分类:同一个业务码在确认和回退两条路径上语义
     * 不同。{@code 23000003} 在确认路径上是「已无未确认金额」——那是既有真实回执复验过的幂等成功,
     * 而在回退路径上它既可能是「已无已确认金额」(同样是幂等),也可能是被拒的原因(例如基本户
     * 余额不足)。这里一律不猜:判成成功会让订单翻回「待核销」而钱还在商户基本户,是资金事故;
     * 判成失败又没有回执依据。所以语义未定的码单独归到 {@link Outcome#NEEDS_REVIEW},停在
     * 「已核销」等人工核对。
     */
    public record HuifuDelayRevert(
            Outcome outcome,
            String code,
            String message,
            String providerTransactionNo
    ) {
        public enum Outcome {
            /** 钱已退回商户延迟户。 */
            SUCCEEDED,
            /** 通道还在处理,结果未定。 */
            PROCESSING,
            /** 通道明确拒绝(例如资金已结算到银行卡、余额不足),钱没有动。 */
            FAILED,
            /** 返回码在这条路径上语义未定,不能当作成功或失败,需要人工核对。 */
            NEEDS_REVIEW
        }
        static HuifuDelayRevert from(Map<String, Object> response) {
            Map<String, Object> data = HuifuPayload.businessData(response);
            String code = HuifuPayload.code(data);
            String message = HuifuPayload.firstNonBlank(
                    HuifuPayload.value(data, "sub_resp_desc", "trans_desc"), HuifuPayload.message(data));
            String status = HuifuPayload.value(data, "trans_stat", "trans_status", "transStat", "transStatus");
            String providerNo = HuifuPayload.value(data, "hf_seq_id", "org_hf_seq_id", "hfSeqId", "orgHfSeqId");
            return new HuifuDelayRevert(classify(status, code), code, message, providerNo);
        }
        /**
         * 在回退路径上语义未定的业务码见 {@link HuifuPayload#undeterminedCode(String)}。
         *
         * <p>它们必须排在交易状态之前判断:通道给这些码时会同时给 {@code trans_stat=F},
         * 先看状态就会把它们当成明确失败(交易确认那条路上正是这样踩过一次坑)。
         */
        private static Outcome classify(String status, String code) {
            String normalizedCode = code == null ? "" : code.trim();
            if (HuifuPayload.undeterminedCode(normalizedCode)) return Outcome.NEEDS_REVIEW;
            if (HuifuPayload.successCode(normalizedCode)) return Outcome.SUCCEEDED;
            if (HuifuPayload.status(status) == HuifuPayload.TransStatus.FAILED) return Outcome.FAILED;
            if ("00000100".equals(normalizedCode)) return Outcome.PROCESSING;
            return normalizedCode.isBlank() ? Outcome.PROCESSING : Outcome.FAILED;
        }
        /** 供日志与故障定位使用的一句话结论。 */
        public String describe() {
            if (!message.isBlank()) return message;
            if (!code.isBlank()) return "Huifu delay confirmation refund returned code " + code;
            return "The provider returned no description";
        }
    }
}