| | |
| | | * |
| | | * <p>业务上是否延迟由订单类型决定:先付钱后核销的订单延迟,核销成功才确认;支付即核销的订单不延迟, |
| | | * 因此不需要确认。 |
| | | * |
| | | * <p>核销被撤销时走反方向:交易确认退款把已确认给商户的钱退回延迟户,重新变成不可结算、不可取现, |
| | | * 等商户重新核销时再确认一次。这条路径的结果分类与确认分开,见 {@link HuifuDelayRevert}。 |
| | | */ |
| | | @Service |
| | | public class HuifuDelayAccountGateway { |
| | |
| | | return execute(request, CONFIRM_QUERY_FUNCTION, "汇付交易确认查询失败"); |
| | | } |
| | | |
| | | /** 把已确认的金额退回原商户账户。 */ |
| | | public HuifuDelayConfirmation refund(HuifuDelayConfirmRefundCommand command) { |
| | | /** 把已确认的金额退回原商户账户(延迟户),撤销核销用它把钱要回来。 */ |
| | | public HuifuDelayRevert refund(HuifuDelayConfirmRefundCommand command) { |
| | | ensureConfigured(); |
| | | initializeSdk(); |
| | | Map<String, Object> request = new LinkedHashMap<>(); |
| | |
| | | "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); |
| | |
| | | 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() { |
| | |
| | | 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"; |
| | | } |
| | | } |
| | | } |