From 68c6a5802964fc8f5b577feb39a44d733ddc9038 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 14 九月 2026 13:30:42 +0800
Subject: [PATCH] refactor(payment): 退役平台自动提现,改由斗拱自动结算并新增结算记录页
---
talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java | 128 ++++++++++++++++++++++++++----------------
1 files changed, 79 insertions(+), 49 deletions(-)
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java
index 419da1f..ab925f1 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java
@@ -1,16 +1,14 @@
package com.talentcard.server.business.admin;
-import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import com.alibaba.fastjson.JSON;
import com.talentcard.server.business.DatabaseRows;
import com.talentcard.server.business.PageResult;
-import com.talentcard.server.business.merchant.AutoWithdrawalAggregationService;
-import com.talentcard.server.business.merchant.AutoWithdrawalResultService;
-import com.talentcard.server.business.merchant.AutoWithdrawalScheduler;
+import com.talentcard.server.business.payment.huifu.HuifuSettlementRecordGateway;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
import com.talentcard.server.common.SensitiveDataCrypto;
@@ -22,35 +20,95 @@
/**
* 后台结算管理。
*
- * <p>后台只能查看进件、银行卡和提现记录,不能修改提现状态:提现记录的状态只由斗拱的异步通知或主动
- * 查询收敛(见 {@code AutoWithdrawalResultService})。进件和银行卡的审核入口在这一版已经下线,
- * 后台保留查询与能力快照刷新。
+ * <p>出款由斗拱按结算周期自动完成(核销确认后的钱进商户基本户,次日自动结算到结算卡),
+ * 平台不再产生提现记录。后台保留进件、银行卡查询、收款能力快照刷新,以及向通道查询
+ * 真实结算记录用于监控每天结了多少到银行卡。
*/
@Service
public class MerchantSettlementAdminService {
private final JdbcTemplate jdbc;
private final SensitiveDataCrypto sensitiveDataCrypto;
private final MerchantPaymentReadinessService readinessService;
- private final AutoWithdrawalScheduler autoWithdrawalScheduler;
- public MerchantSettlementAdminService(JdbcTemplate jdbc, SensitiveDataCrypto sensitiveDataCrypto, MerchantPaymentReadinessService readinessService, AutoWithdrawalScheduler autoWithdrawalScheduler) { this.jdbc = jdbc; this.sensitiveDataCrypto = sensitiveDataCrypto; this.readinessService = readinessService; this.autoWithdrawalScheduler = autoWithdrawalScheduler; }
+ private final HuifuSettlementRecordGateway settlementRecordGateway;
+ public MerchantSettlementAdminService(JdbcTemplate jdbc, SensitiveDataCrypto sensitiveDataCrypto, MerchantPaymentReadinessService readinessService, HuifuSettlementRecordGateway settlementRecordGateway) { this.jdbc = jdbc; this.sensitiveDataCrypto = sensitiveDataCrypto; this.readinessService = readinessService; this.settlementRecordGateway = settlementRecordGateway; }
/**
- * 按业务日补跑一次日汇总并立刻提交、收敛结果。
+ * 结算记录:钱按结算周期自动划到结算卡之后,「每天结了多少」只能问通道。
*
- * <p>定时任务汇总的是「前一自然日」,当天核销的订单要等到第二天凌晨才会出账;排查和验收都等不起,
- * 所以这里提供一次显式补跑。它不接受「不传日期」:隐式取当天容易误刷历史,日期必须由调用方给定。
+ * <p>按门店取该商户当前生效的斗拱子商户号,逐商户查询结算记录后合并成一条时间倒序的列表。
+ * 通道查询失败只影响这一条商户,不会让整页失败——监控页最重要的是「看得到」。
*/
- public Map<String, Object> runAutoWithdrawalAggregation(LocalDate businessDate) {
- if (businessDate == null) throw new BusinessException(ErrorCode.BAD_REQUEST, "请指定业务日 businessDate(yyyyMMdd)");
- AutoWithdrawalAggregationService.Result result = autoWithdrawalScheduler.runForBusinessDate(businessDate);
+ public Map<String, Object> settlements(String beginDate, String endDate, int size) {
+ List<Map<String, Object>> merchants = rows("""
+ select a.merchant_id, a.provider_huifu_id, m.merchant_name, s.store_name
+ from merchant_onboarding_application a
+ join merchant m on m.id = a.merchant_id
+ join merchant_store s on s.id = a.store_id
+ where a.status = 'approved' and a.provider_huifu_id is not null and a.provider_huifu_id <> ''
+ and a.id = (select max(newest.id) from merchant_onboarding_application newest
+ where newest.merchant_id = a.merchant_id)
+ order by a.merchant_id asc
+ """);
+ List<Map<String, Object>> records = new ArrayList<>();
+ List<String> failures = new ArrayList<>();
+ int limit = Math.min(Math.max(size, 1), 100);
+ for (Map<String, Object> merchant : merchants) {
+ String huifuId = String.valueOf(merchant.get("providerHuifuId"));
+ try {
+ HuifuSettlementRecordGateway.SettlementRecords response =
+ settlementRecordGateway.query(huifuId, beginDate, endDate, limit);
+ if (!response.succeeded()) {
+ failures.add(String.valueOf(merchant.get("merchantName")) + ":" + response.message());
+ continue;
+ }
+ records.addAll(decode(response.rawRecords(), merchant));
+ } catch (RuntimeException exception) {
+ failures.add(String.valueOf(merchant.get("merchantName")) + ":查询失败");
+ }
+ }
+ records.sort((left, right) -> String.valueOf(right.get("settleDate"))
+ .compareTo(String.valueOf(left.get("settleDate"))));
Map<String, Object> view = new LinkedHashMap<>();
- view.put("businessDate", result.businessDate());
- view.put("merchantsAggregated", result.merchantsAggregated());
- view.put("merchantsSkipped", result.merchantsSkipped());
- view.put("merchantsFailed", result.merchantsFailed());
- view.put("ordersAggregated", result.ordersAggregated());
- view.put("amountCents", result.amountCents());
+ view.put("beginDate", beginDate);
+ view.put("endDate", endDate);
+ view.put("records", records.size() > limit ? records.subList(0, limit) : records);
+ view.put("total", records.size());
+ view.put("failures", failures);
return view;
+ }
+
+ /** 把通道返回的结算记录 JSON 转成后台要展示的字段;只读展示,不做金额换算以外的加工。 */
+ private List<Map<String, Object>> decode(String rawRecords, Map<String, Object> merchant) {
+ List<Map<String, Object>> decoded = new ArrayList<>();
+ if (rawRecords == null || rawRecords.isBlank()) return decoded;
+ List<Map<String, Object>> parsed;
+ try {
+ parsed = JSON.parseObject(rawRecords, new com.alibaba.fastjson.TypeReference<>() { });
+ } catch (RuntimeException exception) {
+ return decoded;
+ }
+ if (parsed == null) return decoded;
+ for (Map<String, Object> record : parsed) {
+ Map<String, Object> row = new LinkedHashMap<>();
+ row.put("merchantId", merchant.get("merchantId"));
+ row.put("merchantName", merchant.get("merchantName"));
+ row.put("storeName", merchant.get("storeName"));
+ row.put("providerTransactionNo", record.get("trans_id"));
+ row.put("settleDate", record.get("trans_date"));
+ row.put("settleAmountCents", cents(record.get("trans_amt")));
+ row.put("feeAmountCents", cents(record.get("fee_amt")));
+ row.put("settleCycle", record.get("settle_cycle"));
+ row.put("status", record.get("trans_stat"));
+ row.put("cardMasked", record.get("card_no"));
+ row.put("cardHolder", record.get("card_name"));
+ decoded.add(row);
+ }
+ return decoded;
+ }
+
+ private Long cents(Object yuan) {
+ if (yuan == null) return null;
+ return com.talentcard.server.business.payment.huifu.HuifuPayload.amountInCent(String.valueOf(yuan));
}
@Transactional(readOnly = true)
@@ -79,34 +137,6 @@
});
return result;
}
- /**
- * 提现记录只读:状态由斗拱的通知或查询收敛,后台不能人工审核或改写。列表只展示三种对外状态,
- * 因此筛选值也按「处理中 / 已到账 / 提现失败」翻译成库里的原始状态。
- */
- @Transactional(readOnly = true)
- public PageResult<Map<String, Object>> withdrawals(int page, int size, String merchantName, String contactMobile, String merchantType, String status, String startDate, String endDate) {
- String available = "greatest(0, coalesce((select sum(o.pay_amount) from benefit_order o where o.merchant_id = w.merchant_id and (o.store_id is null or o.store_id = w.store_id) and o.order_status = 1 and o.verify_status = 1 and o.refund_status = 0 and o.withdrawal_status = 0 and o.deleted = 0), 0) - coalesce((select sum(w2.amount_cents) from merchant_withdrawal w2 where w2.merchant_id = w.merchant_id and w2.store_id = w.store_id and w2.status in ('pending', 'processing', 'paid')), 0))";
- String select = "select w.*, c.bank_name, c.card_masked, m.merchant_name, m.merchant_type, m.contact_mobile, s.store_name, " + available + " as available_amount_cents from merchant_withdrawal w join merchant_bank_card c on c.id = w.bank_card_id join merchant m on m.id = w.merchant_id join merchant_store s on s.id = w.store_id";
- String count = "select count(*) from merchant_withdrawal w join merchant_bank_card c on c.id = w.bank_card_id join merchant m on m.id = w.merchant_id join merchant_store s on s.id = w.store_id";
- PageResult<Map<String, Object>> result = page(select, count, "w", page, size, merchantName, contactMobile,
- merchantType, null, startDate, endDate, withdrawalStatusCondition(status));
- result.records().forEach(row -> row.put("status",
- AutoWithdrawalResultService.displayStatus(row.get("status") == null ? "" : String.valueOf(row.get("status")))));
- return result;
- }
-
- /**
- * 对外三种状态到库里原始状态的映射条件,由 {@link AutoWithdrawalResultService#rawStatuses} 推导。
- * 没有筛选时不加条件;筛选取值不认识是「筛不出记录」,不是「忽略筛选」。
- */
- private String withdrawalStatusCondition(String status) {
- if (status == null || status.isBlank()) return "";
- List<String> rawStatuses = AutoWithdrawalResultService.rawStatuses(status);
- if (rawStatuses.isEmpty()) return " and 1 = 0";
- return " and w.status in (" + rawStatuses.stream().map(value -> "'" + value + "'")
- .collect(java.util.stream.Collectors.joining(", ")) + ")";
- }
-
/** Provider-confirmed settlement and encashment capability of one approved sub-merchant. */
public Map<String, Object> paymentCapability(Long applicationId) { return readinessService.capability(applicationId); }
--
Gitblit v1.7.1