From 5c33cf1f2622d4470b861cb041024ead1775c828 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 16 九月 2026 18:05:27 +0800
Subject: [PATCH] feat(payment): 撤销核销把已确认的资金退回延迟户
---
talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuDelayAccountGateway.java | 82 ++++++++++++++++++++++++++++++++++++++--
1 files changed, 77 insertions(+), 5 deletions(-)
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuDelayAccountGateway.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuDelayAccountGateway.java
index 83730d6..496ec68 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuDelayAccountGateway.java
+++ b/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";
+ }
+ }
}
--
Gitblit v1.7.1