| | |
| | | private final HuifuEnterpriseOnboardingGateway enterpriseOnboardingGateway; |
| | | private final HuifuMerchantWechatConfigGateway merchantWechatConfigGateway; |
| | | private final HuifuMerchantBusinessOpenGateway merchantBusinessOpenGateway; |
| | | private final HuifuMerchantWithdrawalService withdrawalService; |
| | | private final HuifuUserBusinessOpenGateway userBusinessOpenGateway; |
| | | private final HuifuPaymentProperties huifuPaymentProperties; |
| | | private final HuifuPictureUploadGateway pictureUploadGateway; |
| | | private final MerchantPaymentReadinessService paymentReadinessService; |
| | | |
| | | public MerchantSettlementService(JdbcTemplate jdbc, SensitiveDataCrypto sensitiveDataCrypto, HuifuIndividualOnboardingGateway individualOnboardingGateway, HuifuEnterpriseOnboardingGateway enterpriseOnboardingGateway, HuifuMerchantWechatConfigGateway merchantWechatConfigGateway, HuifuMerchantBusinessOpenGateway merchantBusinessOpenGateway, HuifuMerchantWithdrawalService withdrawalService, HuifuUserBusinessOpenGateway userBusinessOpenGateway, HuifuPaymentProperties huifuPaymentProperties, HuifuPictureUploadGateway pictureUploadGateway, MerchantPaymentReadinessService paymentReadinessService) { |
| | | public MerchantSettlementService(JdbcTemplate jdbc, SensitiveDataCrypto sensitiveDataCrypto, HuifuIndividualOnboardingGateway individualOnboardingGateway, HuifuEnterpriseOnboardingGateway enterpriseOnboardingGateway, HuifuMerchantWechatConfigGateway merchantWechatConfigGateway, HuifuMerchantBusinessOpenGateway merchantBusinessOpenGateway, HuifuUserBusinessOpenGateway userBusinessOpenGateway, HuifuPaymentProperties huifuPaymentProperties, HuifuPictureUploadGateway pictureUploadGateway, MerchantPaymentReadinessService paymentReadinessService) { |
| | | this.jdbc = jdbc; |
| | | this.sensitiveDataCrypto = sensitiveDataCrypto; |
| | | this.individualOnboardingGateway = individualOnboardingGateway; |
| | | this.enterpriseOnboardingGateway = enterpriseOnboardingGateway; |
| | | this.merchantWechatConfigGateway = merchantWechatConfigGateway; |
| | | this.merchantBusinessOpenGateway = merchantBusinessOpenGateway; |
| | | this.withdrawalService = withdrawalService; |
| | | this.userBusinessOpenGateway = userBusinessOpenGateway; |
| | | this.huifuPaymentProperties = huifuPaymentProperties; |
| | | this.pictureUploadGateway = pictureUploadGateway; |
| | |
| | | return Map.of("availableAmountCents", available, "frozenAmountCents", held == null ? 0 : held); |
| | | } |
| | | |
| | | /** |
| | | * 商户端提现记录:只读,且只展示「处理中 / 已到账 / 提现失败」三种状态。自动提现由系统在每日凌晨 |
| | | * 生成并提交给斗拱,商户不能发起提现申请、选择提现金额,也不能修改提现状态(CONTEXT.md「自动提现」)。 |
| | | */ |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> withdrawals(MerchantPrincipal principal, int page, int size) { |
| | | store(principal); int safePage = Math.max(page, 1); int safeSize = Math.min(Math.max(size, 1), 50); |
| | | List<Map<String, Object>> values = rows("select w.*, c.bank_name, c.card_masked from merchant_withdrawal w join merchant_bank_card c on c.id = w.bank_card_id where w.merchant_id = ? and w.store_id = ? order by w.id desc limit ? offset ?", principal.merchantId(), principal.storeId(), safeSize, (safePage - 1) * safeSize).stream().map(this::withdrawal).toList(); |
| | | Long total = jdbc.queryForObject("select count(*) from merchant_withdrawal where merchant_id = ? and store_id = ?", Long.class, principal.merchantId(), principal.storeId()); |
| | | return new PageResult<>(values, safePage, safeSize, total == null ? 0 : total); |
| | | } |
| | | |
| | | @Transactional |
| | | public Map<String, Object> createWithdrawal(MerchantPrincipal principal, MerchantWithdrawalRequest request, String key) { |
| | | requireKey(key); store(principal); requireApproved(principal); |
| | | List<Map<String, Object>> repeated = rows("select * from merchant_withdrawal where merchant_id = ? and idempotency_key = ? limit 1", principal.merchantId(), key.trim()); |
| | | if (!repeated.isEmpty()) return withdrawal(repeated.get(0)); |
| | | Long cardId = number(request.bankCardId()); |
| | | List<Map<String, Object>> cards = rows("select * from merchant_bank_card where id = ? and merchant_id = ? and store_id = ? and status = 'approved' limit 1", cardId, principal.merchantId(), principal.storeId()); |
| | | if (cards.isEmpty()) throw new BusinessException(ErrorCode.CONFLICT, "请选择审核通过的银行卡"); |
| | | long available = ((Number) withdrawalSummary(principal).get("availableAmountCents")).longValue(); |
| | | if (request.amountCents() > available) throw new BusinessException(ErrorCode.CONFLICT, "提现金额不能超过当前可提金额"); |
| | | String no = BusinessIdGenerator.next("MW"); |
| | | jdbc.update("insert into merchant_withdrawal(merchant_id, store_id, withdrawal_no, bank_card_id, amount_cents, idempotency_key) values (?, ?, ?, ?, ?, ?)", principal.merchantId(), principal.storeId(), no, cardId, request.amountCents(), key.trim()); |
| | | Long withdrawalId = jdbc.queryForObject("select id from merchant_withdrawal where withdrawal_no = ?", Long.class, no); |
| | | withdrawalService.dispatch(withdrawalId); |
| | | return withdrawal(rows("select w.*, c.bank_name, c.card_masked from merchant_withdrawal w join merchant_bank_card c on c.id = w.bank_card_id where w.withdrawal_no = ?", no).get(0)); |
| | | } |
| | | |
| | | private void requireApproved(MerchantPrincipal principal) { if (!"approved".equals(onboardingStatus(principal).get("status"))) throw new BusinessException(ErrorCode.CONFLICT, "商户进件尚未审核通过"); } |
| | |
| | | private void store(MerchantPrincipal principal) { if (rows("select id from merchant_store where id = ? and merchant_id = ? and status = 1 and deleted = 0", principal.storeId(), principal.merchantId()).isEmpty()) throw new BusinessException(ErrorCode.FORBIDDEN, "当前门店不可操作"); } |
| | | private Map<String, Object> onboarding(Map<String, Object> row) { return Map.of("id", String.valueOf(row.get("id")), "applicationNo", row.get("applicationNo"), "status", row.get("status"), "subjectType", clean(row.get("subjectType")), "reason", clean(row.get("rejectReason")), "submittedAt", String.valueOf(row.get("createdAt"))); } |
| | | private Map<String, Object> bankCard(Map<String, Object> row) { return Map.of("id", String.valueOf(row.get("id")), "bankName", row.get("bankName"), "branchName", row.get("branchName"), "cardMasked", row.get("cardMasked"), "status", row.get("status"), "reason", clean(row.get("rejectReason")), "createdAt", String.valueOf(row.get("createdAt"))); } |
| | | private Map<String, Object> withdrawal(Map<String, Object> row) { Map<String, Object> value = new LinkedHashMap<>(); value.put("id", String.valueOf(row.get("id"))); value.put("withdrawalNo", row.get("withdrawalNo")); value.put("amountCents", row.get("amountCents")); value.put("status", row.get("status")); value.put("reason", clean(row.get("failureReason"))); value.put("bankName", clean(row.get("bankName"))); value.put("cardMasked", clean(row.get("cardMasked"))); value.put("createdAt", String.valueOf(row.get("createdAt"))); value.put("reviewedAt", clean(row.get("reviewedAt"))); value.put("paidAt", clean(row.get("paidAt"))); return value; } |
| | | private Map<String, Object> withdrawal(Map<String, Object> row) { Map<String, Object> value = new LinkedHashMap<>(); value.put("id", String.valueOf(row.get("id"))); value.put("withdrawalNo", row.get("withdrawalNo")); value.put("amountCents", row.get("amountCents")); value.put("status", AutoWithdrawalResultService.displayStatus(row.get("status") == null ? "" : String.valueOf(row.get("status")))); value.put("reason", clean(row.get("failureReason"))); value.put("bankName", clean(row.get("bankName"))); value.put("cardMasked", clean(row.get("cardMasked"))); value.put("createdAt", String.valueOf(row.get("createdAt"))); value.put("reviewedAt", clean(row.get("reviewedAt"))); value.put("paidAt", clean(row.get("paidAt"))); return value; } |
| | | private Map<String, Object> branch(String id, String branchName, String bankName) { return Map.of("id", id, "branchName", branchName, "bankName", bankName); } |
| | | private Object[] appendPaging(List<Object> params, int size, int offset) { Object[] values = new Object[params.size() + 2]; for (int index = 0; index < params.size(); index++) values[index] = params.get(index); values[params.size()] = size; values[params.size() + 1] = offset; return values; } |
| | | private List<Map<String, Object>> rows(String sql, Object... args) { return DatabaseRows.normalize(jdbc.queryForList(sql, args)); } |