From d32e0dee609ab13220908ab28a11390bede07c84 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 12 九月 2026 18:11:00 +0800
Subject: [PATCH] feat(merchant): 自动提现调用斗拱取现并收敛到账结果

---
 talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java |   56 ++++++++++++++++++++++++++++++++++----------------------
 1 files changed, 34 insertions(+), 22 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 b883765..126a829 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
@@ -7,22 +7,28 @@
 
 import com.talentcard.server.business.DatabaseRows;
 import com.talentcard.server.business.PageResult;
+import com.talentcard.server.business.merchant.AutoWithdrawalResultService;
 import com.talentcard.server.common.BusinessException;
 import com.talentcard.server.common.ErrorCode;
 import com.talentcard.server.common.SensitiveDataCrypto;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import com.talentcard.server.business.merchant.HuifuMerchantWithdrawalService;
 import com.talentcard.server.business.merchant.MerchantPaymentReadinessService;
 
+/**
+ * 后台结算管理。
+ *
+ * <p>后台只能查看进件、银行卡和提现记录,不能修改提现状态:提现记录的状态只由斗拱的异步通知或主动
+ * 查询收敛(见 {@code AutoWithdrawalResultService})。进件和银行卡的审核入口在这一版已经下线,
+ * 后台保留查询与能力快照刷新。
+ */
 @Service
 public class MerchantSettlementAdminService {
     private final JdbcTemplate jdbc;
-    private final HuifuMerchantWithdrawalService withdrawalService;
     private final SensitiveDataCrypto sensitiveDataCrypto;
     private final MerchantPaymentReadinessService readinessService;
-    public MerchantSettlementAdminService(JdbcTemplate jdbc, HuifuMerchantWithdrawalService withdrawalService, SensitiveDataCrypto sensitiveDataCrypto, MerchantPaymentReadinessService readinessService) { this.jdbc = jdbc; this.withdrawalService = withdrawalService; this.sensitiveDataCrypto = sensitiveDataCrypto; this.readinessService = readinessService; }
+    public MerchantSettlementAdminService(JdbcTemplate jdbc, SensitiveDataCrypto sensitiveDataCrypto, MerchantPaymentReadinessService readinessService) { this.jdbc = jdbc; this.sensitiveDataCrypto = sensitiveDataCrypto; this.readinessService = readinessService; }
 
     @Transactional(readOnly = true)
     public PageResult<Map<String, Object>> onboardings(int page, int size, String merchantName, String contactMobile, String merchantType, String status, String startDate, String endDate) {
@@ -50,16 +56,33 @@
         });
         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";
-        return page(select, count, "w", page, size, merchantName, contactMobile, merchantType, status, startDate, endDate);
+        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;
     }
 
-    @Transactional public Map<String, Object> reviewBankCard(Long id, String status, String reason, String tokenNo) { return reviewBankCardInternal(id, status, reason, tokenNo); }
-    public Map<String, Object> reviewWithdrawal(Long id, String status, String reason) { if ("paid".equals(normalizeWithdrawalStatus(status))) return withdrawalService.dispatch(id); return update("merchant_withdrawal", id, "failed", reason, "withdrawal"); }
+    /**
+     * 对外三种状态到库里原始状态的映射条件,由 {@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); }
@@ -67,14 +90,14 @@
     /** Re-reads the provider configuration and stores a fresh capability snapshot. */
     public Map<String, Object> refreshPaymentCapability(Long applicationId) { return readinessService.refresh(applicationId); }
 
-    private Map<String,Object> reviewBankCardInternal(Long id,String status,String reason,String tokenNo) { String value=reviewStatus(status); if("approved".equals(value) && (tokenNo==null || !tokenNo.trim().matches("[A-Za-z0-9]{1,32}"))) throw new BusinessException(ErrorCode.BAD_REQUEST,"Huifu withdrawal card token is required when approving a bank card"); int affected=jdbc.update("update merchant_bank_card set status=?, reject_reason=?, provider_token_no=?, reviewed_at=now() where id=? and status='pending'",value,reason==null?"":reason.trim(),"approved".equals(value)?tokenNo.trim():null,id); if(affected==0)throw new BusinessException(ErrorCode.CONFLICT,"Bank card is already handled or does not exist");return Map.of("id",id,"status",value); }
-    private String reviewStatus(String status) { String value=status==null?"":status.trim().toLowerCase(); if(!List.of("approved","rejected").contains(value))throw new BusinessException(ErrorCode.BAD_REQUEST,"Invalid review status");return value; }
-    private String normalizeWithdrawalStatus(String status) { String value=status==null?"":status.trim().toLowerCase(); if(!List.of("paid","failed").contains(value))throw new BusinessException(ErrorCode.BAD_REQUEST,"Invalid withdrawal status");return value; }
-
     private PageResult<Map<String, Object>> page(String query, String count, String tableAlias, int page, int size, String merchantName, String contactMobile, String merchantType, String status, String startDate, String endDate) {
+        return page(query, count, tableAlias, page, size, merchantName, contactMobile, merchantType, status, startDate, endDate, "");
+    }
+
+    private PageResult<Map<String, Object>> page(String query, String count, String tableAlias, int page, int size, String merchantName, String contactMobile, String merchantType, String status, String startDate, String endDate, String extraWhere) {
         int current = Math.max(1, page);
         int limit = Math.min(Math.max(1, size), 100);
-        StringBuilder where = new StringBuilder(" where 1 = 1");
+        StringBuilder where = new StringBuilder(" where 1 = 1" + extraWhere);
         List<Object> args = new ArrayList<>();
         addLike(where, args, "m.merchant_name", merchantName);
         addLike(where, args, "m.contact_mobile", contactMobile);
@@ -117,17 +140,6 @@
             case "513400" -> "四川省|凉山彝族自治州";
             default -> cityCode == null ? "" : String.valueOf(cityCode).trim();
         };
-    }
-    private Map<String, Object> update(String table, Long id, String status, String reason, String label) {
-        String value = status == null ? "" : status.trim().toLowerCase();
-        if (!List.of("approved", "rejected", "paid", "failed").contains(value)) throw new BusinessException(ErrorCode.BAD_REQUEST, "审核状态无效");
-        if ("merchant_onboarding_application".equals(table) && !List.of("approved", "rejected").contains(value)) throw new BusinessException(ErrorCode.BAD_REQUEST, "进件状态无效");
-        if ("merchant_bank_card".equals(table) && !List.of("approved", "rejected").contains(value)) throw new BusinessException(ErrorCode.BAD_REQUEST, "银行卡状态无效");
-        int affected;
-        if ("merchant_withdrawal".equals(table)) affected = jdbc.update("update merchant_withdrawal set status = ?, failure_reason = ?, reviewed_at = now(), paid_at = case when ? = 'paid' then now() else paid_at end where id = ? and status in ('pending','processing')", value, reason == null ? "" : reason.trim(), value, id);
-        else affected = jdbc.update("update " + table + " set status = ?, reject_reason = ?, reviewed_at = now() where id = ? and status = 'pending'", value, reason == null ? "" : reason.trim(), id);
-        if (affected == 0) throw new BusinessException(ErrorCode.CONFLICT, "该" + label + "已处理或不存在");
-        return Map.of("id", id, "status", value);
     }
     private List<Map<String, Object>> rows(String sql, Object... args) { return DatabaseRows.normalize(jdbc.queryForList(sql, args)); }
 }

--
Gitblit v1.7.1