yeg
2026-09-14 063d60e17d8bfa92a5d1a70386548844b379ef7c
fix(payment): 交易确认的幂等成功不再被误判成失败

通道对「已经没有待确认金额」的延迟交易返回 23000003「已无未确认金额」,同时给出
trans_stat=F。此前先看交易状态、再看返回码,于是这个幂等成功被判成失败:补偿任务会
一直认为没确认,每隔几分钟无意义地打一次通道,永远不自愈。

改为先判断这个业务码。用真实回执复验:同样的 23000003 现在正确判为 SUCCEEDED。
2个文件已修改
42 ■■■■■ 已修改文件
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuDelayAccountGateway.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/test/java/com/talentcard/server/business/payment/PaymentFundConfirmationTests.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuDelayAccountGateway.java
@@ -189,6 +189,9 @@
        public enum Outcome { SUCCEEDED, PROCESSING, FAILED }
        /** 通道说「已经没有待确认金额」:这笔延迟交易此前已经确认过,属于幂等成功。 */
        private static final String NOTHING_LEFT_TO_CONFIRM = "23000003";
        public boolean settled() {
            return outcome != Outcome.PROCESSING;
        }
@@ -202,19 +205,26 @@
            String unconfirmed = HuifuPayload.value(data, "unconfirm_amt", "unconfirmAmt");
            String confirmed = HuifuPayload.value(data, "confirmed_amt", "confirmedAmt");
            String providerNo = HuifuPayload.value(data, "hf_seq_id", "org_hf_seq_id", "hfSeqId", "orgHfSeqId");
            return new HuifuDelayConfirmation(classify(status, code), code, message, unconfirmed, confirmed, providerNo);
            return new HuifuDelayConfirmation(
                    classify(status, code, message), code, message, unconfirmed, confirmed, providerNo);
        }
        /**
         * 交易确认的同步响应只有 {@code resp_code} 和金额字段,没有交易状态;受理成功码是
         * {@code 00000000},{@code 00000100} 表示还在处理中。明确的失败码按失败处理。
         * {@code 00000000},{@code 00000100} 表示还在处理中。
         *
         * <p>{@code 23000003 已无未确认金额} 说明这笔延迟交易此前已经确认过(重新确认不会重复分账),
         * 必须按成功处理:否则补偿任务会一直认为没确认,每隔几分钟无意义地打一次通道。
         * 这个判断放在交易状态之前——通道在「没有待确认金额」时同时会给 {@code trans_stat=F},
         * 先看状态就会把这个幂等成功误判成失败。
         */
        private static Outcome classify(String status, String code) {
            boolean accepted = HuifuPayload.successCode(code);
        private static Outcome classify(String status, String code, String message) {
            String normalizedCode = code == null ? "" : code.trim();
            if (NOTHING_LEFT_TO_CONFIRM.equals(normalizedCode)) return Outcome.SUCCEEDED;
            if (HuifuPayload.successCode(normalizedCode)) return Outcome.SUCCEEDED;
            if (HuifuPayload.status(status) == HuifuPayload.TransStatus.FAILED) return Outcome.FAILED;
            if (accepted) return Outcome.SUCCEEDED;
            if ("00000100".equals(code == null ? "" : code.trim())) return Outcome.PROCESSING;
            return code == null || code.isBlank() ? Outcome.PROCESSING : Outcome.FAILED;
            if ("00000100".equals(normalizedCode)) return Outcome.PROCESSING;
            return normalizedCode.isBlank() ? Outcome.PROCESSING : Outcome.FAILED;
        }
        /** 供日志与故障定位使用的一句话结论。 */
talent-card-server/src/test/java/com/talentcard/server/business/payment/PaymentFundConfirmationTests.java
@@ -240,6 +240,24 @@
                "00000000", "交易成功", "0.00", confirmedYuan, "HF-" + System.nanoTime());
    }
    /**
     * 通道说「已无未确认金额」表示这笔延迟交易此前已经确认过(重新确认不会重复分账)。
     * 必须按成功处理:否则补偿任务会一直认为没确认,每隔几分钟无意义地打一次通道。
     */
    @Test
    void nothingLeftToConfirmCountsAsAlreadyConfirmed() {
        String orderNo = createOrder("GROUP_BUY", 100L);
        String paymentNo = insertPaidPayment(orderNo, 100L, "pending");
        when(delayGateway.confirm(any())).thenReturn(new HuifuDelayAccountGateway.HuifuDelayConfirmation(
                HuifuDelayAccountGateway.HuifuDelayConfirmation.Outcome.SUCCEEDED,
                "23000003", "已无未确认金额", "", "", ""));
        assertThat(confirmationService.confirmForOrder(orderId(orderNo))).isEqualTo(
                PaymentFundConfirmationService.CONFIRMED);
        assertThat(jdbc.queryForObject("select funds_confirmation_status from payment_transaction"
                + " where payment_no = ?", String.class, paymentNo)).isEqualTo("confirmed");
    }
    /** 通道还在处理:状态保持 pending,供确认补偿任务下一轮再试。 */
    private HuifuDelayAccountGateway.HuifuDelayConfirmation processing() {
        return new HuifuDelayAccountGateway.HuifuDelayConfirmation(