package com.talentcard.server.business.admin;
|
|
import java.util.ArrayList;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
import com.talentcard.server.business.DatabaseRows;
|
import com.talentcard.server.business.PageResult;
|
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;
|
|
@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; }
|
|
@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) {
|
return page("select a.*, m.merchant_name, m.merchant_type, m.contact_mobile, 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", "select count(*) from merchant_onboarding_application a join merchant m on m.id = a.merchant_id join merchant_store s on s.id = a.store_id", "a", page, size, merchantName, contactMobile, merchantType, status, startDate, endDate);
|
}
|
@Transactional(readOnly = true)
|
public Map<String, Object> onboarding(Long id) {
|
List<Map<String, Object>> rows = rows("select a.*, a.merchant_name as onboarding_merchant_name, m.merchant_name, m.merchant_type, m.contact_mobile, 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.id = ?", id);
|
if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "商户进件不存在");
|
Map<String, Object> result = rows.get(0);
|
// 个人主体的历史记录可能使用 individual、personal 或中文类型值;
|
// 只要已保存加密身份证号,后台详情均应解密后完整展示。
|
String encrypted = String.valueOf(result.get("subjectIdNoEncrypted"));
|
if (!encrypted.isBlank() && !"null".equals(encrypted)) result.put("subjectIdNo", sensitiveDataCrypto.decrypt(encrypted));
|
return result;
|
}
|
@Transactional(readOnly = true)
|
public PageResult<Map<String, Object>> bankCards(int page, int size, String merchantName, String contactMobile, String merchantType, String status, String startDate, String endDate) {
|
PageResult<Map<String, Object>> result = page("select c.*, m.merchant_name, m.merchant_type, m.contact_mobile, s.store_name from merchant_bank_card c join merchant m on m.id = c.merchant_id join merchant_store s on s.id = c.store_id", "select count(*) from merchant_bank_card c join merchant m on m.id = c.merchant_id join merchant_store s on s.id = c.store_id", "c", page, size, merchantName, contactMobile, merchantType, status, startDate, endDate);
|
result.records().forEach(row -> {
|
String encrypted = String.valueOf(row.remove("cardNoEncrypted"));
|
if (!encrypted.isBlank() && !"null".equals(encrypted)) row.put("cardNo", sensitiveDataCrypto.decrypt(encrypted));
|
else row.put("cardNo", row.get("cardMasked"));
|
row.put("cityName", bankCardCityName(row.get("cityCode")));
|
});
|
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.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);
|
}
|
|
@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"); }
|
|
/** Provider-confirmed settlement and encashment capability of one approved sub-merchant. */
|
public Map<String, Object> paymentCapability(Long applicationId) { return readinessService.capability(applicationId); }
|
|
/** 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) {
|
int current = Math.max(1, page);
|
int limit = Math.min(Math.max(1, size), 100);
|
StringBuilder where = new StringBuilder(" where 1 = 1");
|
List<Object> args = new ArrayList<>();
|
addLike(where, args, "m.merchant_name", merchantName);
|
addLike(where, args, "m.contact_mobile", contactMobile);
|
addEqual(where, args, "m.merchant_type", merchantType);
|
addEqual(where, args, tableAlias + ".status", status);
|
addDate(where, args, tableAlias + ".created_at", startDate, true);
|
addDate(where, args, tableAlias + ".created_at", endDate, false);
|
List<Object> rowArgs = new ArrayList<>(args);
|
rowArgs.add(limit);
|
rowArgs.add((current - 1) * limit);
|
List<Map<String, Object>> values = rows(query + where + " order by " + tableAlias + ".created_at desc, " + tableAlias + ".id desc limit ? offset ?", rowArgs.toArray());
|
Long total = jdbc.queryForObject(count + where, Long.class, args.toArray());
|
return new PageResult<>(values, current, limit, total == null ? 0 : total);
|
}
|
private void addLike(StringBuilder where, List<Object> args, String column, String value) { if (value != null && !value.trim().isEmpty()) { where.append(" and ").append(column).append(" like ?"); args.add("%" + value.trim() + "%"); } }
|
private void addEqual(StringBuilder where, List<Object> args, String column, String value) { if (value != null && !value.trim().isEmpty()) { where.append(" and ").append(column).append(" = ?"); args.add(value.trim()); } }
|
private void addDate(StringBuilder where, List<Object> args, String column, String value, boolean start) { if (value != null && value.matches("\\d{4}-\\d{2}-\\d{2}")) { where.append(" and ").append(column).append(start ? " >= ?" : " < date_add(?, interval 1 day)"); args.add(value); } }
|
private String bankCardCityName(Object cityCode) {
|
return switch (cityCode == null ? "" : String.valueOf(cityCode).trim()) {
|
case "510100" -> "四川省|成都市";
|
case "510300" -> "四川省|自贡市";
|
case "510400" -> "四川省|攀枝花市";
|
case "510500" -> "四川省|泸州市";
|
case "510600" -> "四川省|德阳市";
|
case "510700" -> "四川省|绵阳市";
|
case "510800" -> "四川省|广元市";
|
case "510900" -> "四川省|遂宁市";
|
case "511000" -> "四川省|内江市";
|
case "511100" -> "四川省|乐山市";
|
case "511300" -> "四川省|南充市";
|
case "511400" -> "四川省|眉山市";
|
case "511500" -> "四川省|宜宾市";
|
case "511600" -> "四川省|广安市";
|
case "511700" -> "四川省|达州市";
|
case "511800" -> "四川省|雅安市";
|
case "511900" -> "四川省|巴中市";
|
case "512000" -> "四川省|资阳市";
|
case "513200" -> "四川省|阿坝藏族羌族自治州";
|
case "513300" -> "四川省|甘孜藏族自治州";
|
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)); }
|
}
|