|
@@ -2,32 +2,52 @@ package com.payment.platform.module.payment.batch.service;
|
|
|
|
|
|
|
|
import com.alipay.api.AlipayApiException;
|
|
import com.alipay.api.AlipayApiException;
|
|
|
import com.alipay.api.AlipayClient;
|
|
import com.alipay.api.AlipayClient;
|
|
|
|
|
+import com.alipay.api.domain.AccDetailModel;
|
|
|
import com.alipay.api.domain.AlipayFundAuthorizeUniApplyModel;
|
|
import com.alipay.api.domain.AlipayFundAuthorizeUniApplyModel;
|
|
|
import com.alipay.api.domain.AlipayFundAuthorizeUniQueryModel;
|
|
import com.alipay.api.domain.AlipayFundAuthorizeUniQueryModel;
|
|
|
|
|
+import com.alipay.api.domain.AlipayFundBatchCloseModel;
|
|
|
|
|
+import com.alipay.api.domain.AlipayFundBatchCreateModel;
|
|
|
|
|
+import com.alipay.api.domain.AlipayFundBatchDetailQueryModel;
|
|
|
|
|
+import com.alipay.api.domain.AlipayFundTransRenderPayModel;
|
|
|
import com.alipay.api.domain.AuthParticipantInfo;
|
|
import com.alipay.api.domain.AuthParticipantInfo;
|
|
|
|
|
+import com.alipay.api.domain.Participant;
|
|
|
|
|
+import com.alipay.api.domain.TransOrderDetail;
|
|
|
|
|
+import com.alipay.api.domain.TransferSceneReportInfo;
|
|
|
import com.alipay.api.request.AlipayFundAuthorizeUniApplyRequest;
|
|
import com.alipay.api.request.AlipayFundAuthorizeUniApplyRequest;
|
|
|
import com.alipay.api.request.AlipayFundAuthorizeUniQueryRequest;
|
|
import com.alipay.api.request.AlipayFundAuthorizeUniQueryRequest;
|
|
|
|
|
+import com.alipay.api.request.AlipayFundBatchCloseRequest;
|
|
|
|
|
+import com.alipay.api.request.AlipayFundBatchCreateRequest;
|
|
|
|
|
+import com.alipay.api.request.AlipayFundBatchDetailQueryRequest;
|
|
|
|
|
+import com.alipay.api.request.AlipayFundTransRenderPayRequest;
|
|
|
import com.alipay.api.response.AlipayFundAuthorizeUniApplyResponse;
|
|
import com.alipay.api.response.AlipayFundAuthorizeUniApplyResponse;
|
|
|
import com.alipay.api.response.AlipayFundAuthorizeUniQueryResponse;
|
|
import com.alipay.api.response.AlipayFundAuthorizeUniQueryResponse;
|
|
|
|
|
+import com.alipay.api.response.AlipayFundBatchCloseResponse;
|
|
|
|
|
+import com.alipay.api.response.AlipayFundBatchCreateResponse;
|
|
|
|
|
+import com.alipay.api.response.AlipayFundBatchDetailQueryResponse;
|
|
|
|
|
+import com.alipay.api.response.AlipayFundTransRenderPayResponse;
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.payment.platform.common.exception.BusinessException;
|
|
import com.payment.platform.common.exception.BusinessException;
|
|
|
import com.payment.platform.common.response.PageResult;
|
|
import com.payment.platform.common.response.PageResult;
|
|
|
import com.payment.platform.common.utils.ExcelUtil;
|
|
import com.payment.platform.common.utils.ExcelUtil;
|
|
|
import com.payment.platform.common.utils.SnowflakeIdGenerator;
|
|
import com.payment.platform.common.utils.SnowflakeIdGenerator;
|
|
|
import com.payment.platform.core.alipay.AlipayClientFactory;
|
|
import com.payment.platform.core.alipay.AlipayClientFactory;
|
|
|
|
|
+import com.payment.platform.module.payment.batch.dto.BatchCreateDTO;
|
|
|
import com.payment.platform.module.payment.batch.entity.BatchAuthorizeEntity;
|
|
import com.payment.platform.module.payment.batch.entity.BatchAuthorizeEntity;
|
|
|
import com.payment.platform.module.payment.batch.entity.BatchDetailEntity;
|
|
import com.payment.platform.module.payment.batch.entity.BatchDetailEntity;
|
|
|
import com.payment.platform.module.payment.batch.entity.BatchOrderEntity;
|
|
import com.payment.platform.module.payment.batch.entity.BatchOrderEntity;
|
|
|
import com.payment.platform.module.payment.batch.mapper.BatchAuthorizeMapper;
|
|
import com.payment.platform.module.payment.batch.mapper.BatchAuthorizeMapper;
|
|
|
import com.payment.platform.module.payment.batch.mapper.BatchDetailMapper;
|
|
import com.payment.platform.module.payment.batch.mapper.BatchDetailMapper;
|
|
|
import com.payment.platform.module.payment.batch.mapper.BatchOrderMapper;
|
|
import com.payment.platform.module.payment.batch.mapper.BatchOrderMapper;
|
|
|
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
-import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
import java.time.OffsetDateTime;
|
|
import java.time.OffsetDateTime;
|
|
|
|
|
+import java.time.ZoneId;
|
|
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
@@ -54,6 +74,8 @@ public class AlipayBatchPayService {
|
|
|
|
|
|
|
|
private final AlipayClientFactory alipayClientFactory;
|
|
private final AlipayClientFactory alipayClientFactory;
|
|
|
private final BatchAuthorizeMapper batchAuthorizeMapper;
|
|
private final BatchAuthorizeMapper batchAuthorizeMapper;
|
|
|
|
|
+ private final BatchOrderMapper batchOrderMapper;
|
|
|
|
|
+ private final BatchDetailMapper batchDetailMapper;
|
|
|
|
|
|
|
|
/** alipay.fund.authorize.uni.apply — 生成制单授权短链接(PC 渠道) */
|
|
/** alipay.fund.authorize.uni.apply — 生成制单授权短链接(PC 渠道) */
|
|
|
@Transactional
|
|
@Transactional
|
|
@@ -141,4 +163,332 @@ public class AlipayBatchPayService {
|
|
|
throw new BusinessException(400, "查询授权状态失败: " + e.getMessage());
|
|
throw new BusinessException(400, "查询授权状态失败: " + e.getMessage());
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 批次 ====================
|
|
|
|
|
+
|
|
|
|
|
+ private static final String BATCH_PRODUCT_CODE = "BATCH_API_TO_ACC";
|
|
|
|
|
+ private static final String BATCH_BIZ_SCENE = "STANDARD_MESSAGE_BATCH_PAY";
|
|
|
|
|
+
|
|
|
|
|
+ /** alipay.fund.batch.create — 创建批量付款单据(幂等: out_batch_no 唯一) */
|
|
|
|
|
+ @Transactional
|
|
|
|
|
+ public Map<String, Object> batchCreate(BatchCreateDTO dto) {
|
|
|
|
|
+ if (dto.getOutBatchNo() == null || dto.getOutBatchNo().isBlank())
|
|
|
|
|
+ dto.setOutBatchNo(SnowflakeIdGenerator.nextIdStr());
|
|
|
|
|
+ // 幂等保护: 同单号已存在则拒绝,避免重复支付
|
|
|
|
|
+ if (batchOrderMapper.selectCount(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
|
|
|
|
|
+ .eq(BatchOrderEntity::getOutBatchNo, dto.getOutBatchNo())) > 0)
|
|
|
|
|
+ throw new BusinessException(400, "批次外部单号已存在,请勿重复创建");
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal total = BigDecimal.ZERO;
|
|
|
|
|
+ List<TransOrderDetail> transList = new ArrayList<>();
|
|
|
|
|
+ for (BatchCreateDTO.BatchDetailDTO d : dto.getDetails()) {
|
|
|
|
|
+ if (d.getAmount().compareTo(new BigDecimal("1")) < 0)
|
|
|
|
|
+ throw new BusinessException(400, "明细金额最低 1 元: " + d.getOutBizNo());
|
|
|
|
|
+ total = total.add(d.getAmount());
|
|
|
|
|
+ TransOrderDetail t = new TransOrderDetail();
|
|
|
|
|
+ t.setOutBizNo(d.getOutBizNo());
|
|
|
|
|
+ t.setTransAmount(d.getAmount().toPlainString());
|
|
|
|
|
+ t.setRemark(d.getRemark());
|
|
|
|
|
+ Participant payee = new Participant();
|
|
|
|
|
+ String idType = d.getPayeeIdentityType() != null ? d.getPayeeIdentityType() : "ALIPAY_LOGON_ID";
|
|
|
|
|
+ if ("ALIPAY_LOGON_ID".equals(idType) && (d.getPayeeName() == null || d.getPayeeName().isBlank()))
|
|
|
|
|
+ throw new BusinessException(400, "收款方为账号时姓名必填: " + d.getOutBizNo());
|
|
|
|
|
+ payee.setIdentity(d.getPayeeIdentity());
|
|
|
|
|
+ payee.setIdentityType(idType);
|
|
|
|
|
+ payee.setName(d.getPayeeName());
|
|
|
|
|
+ t.setPayeeInfo(payee);
|
|
|
|
|
+ transList.add(t);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ AlipayFundBatchCreateModel model = new AlipayFundBatchCreateModel();
|
|
|
|
|
+ model.setOutBatchNo(dto.getOutBatchNo());
|
|
|
|
|
+ model.setTotalTransAmount(total.toPlainString());
|
|
|
|
|
+ model.setTotalCount(String.valueOf(dto.getDetails().size()));
|
|
|
|
|
+ model.setProductCode(BATCH_PRODUCT_CODE);
|
|
|
|
|
+ model.setBizScene(BATCH_BIZ_SCENE);
|
|
|
|
|
+ model.setOrderTitle(dto.getOrderTitle());
|
|
|
|
|
+ if (dto.getTimeExpire() != null) model.setTimeExpire(dto.getTimeExpire());
|
|
|
|
|
+ if (dto.getRemark() != null) model.setRemark(dto.getRemark());
|
|
|
|
|
+ // 付款方 + 制单授权协议
|
|
|
|
|
+ Participant payer = new Participant();
|
|
|
|
|
+ payer.setIdentity(dto.getPayerUid());
|
|
|
|
|
+ payer.setIdentityType("ALIPAY_USER_ID");
|
|
|
|
|
+ if (dto.getAgreementNo() != null && !dto.getAgreementNo().isBlank()) {
|
|
|
|
|
+ Map<String, String> ext = new LinkedHashMap<>();
|
|
|
|
|
+ ext.put("agreement_no", dto.getAgreementNo());
|
|
|
|
|
+ try { payer.setExtInfo(oMapper.writeValueAsString(ext)); }
|
|
|
|
|
+ catch (Exception e) { throw new BusinessException(400, "序列化 ext_info 失败"); }
|
|
|
|
|
+ }
|
|
|
|
|
+ model.setPayerInfo(payer);
|
|
|
|
|
+ model.setTransOrderList(transList);
|
|
|
|
|
+ // 转账场景报备(26年新接入必传)
|
|
|
|
|
+ model.setTransferSceneName(dto.getTransferSceneName());
|
|
|
|
|
+ if (dto.getTransferSceneReportInfos() != null && !dto.getTransferSceneReportInfos().isEmpty()) {
|
|
|
|
|
+ List<TransferSceneReportInfo> infos = dto.getTransferSceneReportInfos().stream()
|
|
|
|
|
+ .map(m -> { TransferSceneReportInfo i = new TransferSceneReportInfo();
|
|
|
|
|
+ i.setInfoType(m.get("info_type")); i.setInfoContent(m.get("info_content")); return i; })
|
|
|
|
|
+ .collect(java.util.stream.Collectors.toList());
|
|
|
|
|
+ model.setTransferSceneReportInfos(infos);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ AlipayFundBatchCreateRequest request = new AlipayFundBatchCreateRequest();
|
|
|
|
|
+ request.setBizModel(model);
|
|
|
|
|
+ AlipayFundBatchCreateResponse response =
|
|
|
|
|
+ alipayClientFactory.getClient(dto.getEnterpriseId(), BIZ_TYPE).certificateExecute(request);
|
|
|
|
|
+ if (!response.isSuccess()) {
|
|
|
|
|
+ // 幂等兜底: UNIQUE_VIOLATION 说明支付宝侧已受理同单号批次,查库返回已受理信息而非报错
|
|
|
|
|
+ if ("UNIQUE_VIOLATION".equals(response.getSubCode())) {
|
|
|
|
|
+ BatchOrderEntity existing = batchOrderMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
|
|
|
|
|
+ .eq(BatchOrderEntity::getOutBatchNo, dto.getOutBatchNo()));
|
|
|
|
|
+ if (existing != null)
|
|
|
|
|
+ return Map.of("out_batch_no", existing.getOutBatchNo(),
|
|
|
|
|
+ "batch_trans_id", existing.getBatchTransId() != null ? existing.getBatchTransId() : "",
|
|
|
|
|
+ "status", existing.getStatus());
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException(400, "创建批次失败: " + response.getMsg() + " (" + response.getSubCode() + ")");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BatchOrderEntity order = new BatchOrderEntity();
|
|
|
|
|
+ order.setEnterpriseId(dto.getEnterpriseId());
|
|
|
|
|
+ order.setTenantId(dto.getTenantId());
|
|
|
|
|
+ order.setOutBatchNo(dto.getOutBatchNo());
|
|
|
|
|
+ order.setBatchTransId(response.getBatchTransId());
|
|
|
|
|
+ order.setTotalAmount(total);
|
|
|
|
|
+ order.setTotalCount(dto.getDetails().size());
|
|
|
|
|
+ order.setOrderTitle(dto.getOrderTitle());
|
|
|
|
|
+ order.setStatus("INIT");
|
|
|
|
|
+ order.setPayerUid(dto.getPayerUid());
|
|
|
|
|
+ order.setAgreementNo(dto.getAgreementNo());
|
|
|
|
|
+ order.setTransferSceneName(dto.getTransferSceneName());
|
|
|
|
|
+ if (dto.getTransferSceneReportInfos() != null)
|
|
|
|
|
+ try { order.setTransferSceneReportInfos(oMapper.writeValueAsString(dto.getTransferSceneReportInfos())); }
|
|
|
|
|
+ catch (Exception ignored) { }
|
|
|
|
|
+ order.setRemark(dto.getRemark());
|
|
|
|
|
+ batchOrderMapper.insert(order);
|
|
|
|
|
+
|
|
|
|
|
+ for (BatchCreateDTO.BatchDetailDTO d : dto.getDetails()) {
|
|
|
|
|
+ BatchDetailEntity de = new BatchDetailEntity();
|
|
|
|
|
+ de.setEnterpriseId(dto.getEnterpriseId());
|
|
|
|
|
+ de.setTenantId(dto.getTenantId());
|
|
|
|
|
+ de.setBatchId(order.getId());
|
|
|
|
|
+ de.setOutBizNo(d.getOutBizNo());
|
|
|
|
|
+ de.setAmount(d.getAmount());
|
|
|
|
|
+ de.setRemark(d.getRemark());
|
|
|
|
|
+ de.setPayeeIdentity(d.getPayeeIdentity());
|
|
|
|
|
+ de.setPayeeIdentityType(d.getPayeeIdentityType() != null ? d.getPayeeIdentityType() : "ALIPAY_LOGON_ID");
|
|
|
|
|
+ de.setPayeeName(d.getPayeeName());
|
|
|
|
|
+ de.setStatus("INIT");
|
|
|
|
|
+ batchDetailMapper.insert(de);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Map.of("out_batch_no", dto.getOutBatchNo(),
|
|
|
|
|
+ "batch_trans_id", response.getBatchTransId() != null ? response.getBatchTransId() : "",
|
|
|
|
|
+ "status", "INIT");
|
|
|
|
|
+ } catch (AlipayApiException e) {
|
|
|
|
|
+ throw new BusinessException(400, "创建批次失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** alipay.fund.trans.render.pay — 生成 PC 支付页链接 */
|
|
|
|
|
+ public Map<String, String> renderPay(String enterpriseId, String outBatchNo) {
|
|
|
|
|
+ BatchOrderEntity order = batchOrderMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
|
|
|
|
|
+ .eq(BatchOrderEntity::getOutBatchNo, outBatchNo));
|
|
|
|
|
+ if (order == null) throw new BusinessException(404, "批次不存在");
|
|
|
|
|
+ if (!"INIT".equals(order.getStatus()))
|
|
|
|
|
+ throw new BusinessException(400, "仅受理中的批次可支付,当前状态: " + order.getStatus());
|
|
|
|
|
+ try {
|
|
|
|
|
+ AlipayFundTransRenderPayModel model = new AlipayFundTransRenderPayModel();
|
|
|
|
|
+ model.setProductCode(BATCH_PRODUCT_CODE);
|
|
|
|
|
+ model.setBizScene(BATCH_BIZ_SCENE);
|
|
|
|
|
+ model.setOrderId(order.getBatchTransId());
|
|
|
|
|
+ AlipayFundTransRenderPayRequest request = new AlipayFundTransRenderPayRequest();
|
|
|
|
|
+ request.setBizModel(model);
|
|
|
|
|
+ AlipayFundTransRenderPayResponse response =
|
|
|
|
|
+ alipayClientFactory.getClient(enterpriseId, BIZ_TYPE).pageExecute(request);
|
|
|
|
|
+ if (response == null || response.getBody() == null)
|
|
|
|
|
+ throw new BusinessException(400, "生成支付页面失败: 无响应");
|
|
|
|
|
+ order.setPayUrl(response.getBody());
|
|
|
|
|
+ batchOrderMapper.updateById(order);
|
|
|
|
|
+ return Map.of("pay_url", response.getBody());
|
|
|
|
|
+ } catch (AlipayApiException e) {
|
|
|
|
|
+ throw new BusinessException(400, "生成支付页面失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** alipay.fund.batch.detail.query — 查询批次+明细状态并回写 DB */
|
|
|
|
|
+ public Map<String, Object> batchQuery(String enterpriseId, String outBatchNo) {
|
|
|
|
|
+ BatchOrderEntity order = batchOrderMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
|
|
|
|
|
+ .eq(BatchOrderEntity::getOutBatchNo, outBatchNo));
|
|
|
|
|
+ if (order == null) throw new BusinessException(404, "批次不存在");
|
|
|
|
|
+ try {
|
|
|
|
|
+ AlipayFundBatchDetailQueryModel model = new AlipayFundBatchDetailQueryModel();
|
|
|
|
|
+ model.setOutBatchNo(outBatchNo);
|
|
|
|
|
+ model.setProductCode(BATCH_PRODUCT_CODE);
|
|
|
|
|
+ model.setBizScene(BATCH_BIZ_SCENE);
|
|
|
|
|
+ AlipayFundBatchDetailQueryRequest request = new AlipayFundBatchDetailQueryRequest();
|
|
|
|
|
+ request.setBizModel(model);
|
|
|
|
|
+ AlipayFundBatchDetailQueryResponse response =
|
|
|
|
|
+ alipayClientFactory.getClient(enterpriseId, BIZ_TYPE).certificateExecute(request);
|
|
|
|
|
+ if (!response.isSuccess())
|
|
|
|
|
+ throw new BusinessException(400, "查询批次失败: " + response.getMsg());
|
|
|
|
|
+ // 批次状态回写(SDK 字段 batch_status,非 status — 已 javap 实证)
|
|
|
|
|
+ if (response.getBatchStatus() != null && !response.getBatchStatus().equals(order.getStatus())) {
|
|
|
|
|
+ order.setStatus(response.getBatchStatus());
|
|
|
|
|
+ batchOrderMapper.updateById(order);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 明细状态回写(SDK 字段 acc_detail_list,按 out_biz_no 匹配本地明细)
|
|
|
|
|
+ if (response.getAccDetailList() != null) {
|
|
|
|
|
+ for (AccDetailModel m : response.getAccDetailList()) {
|
|
|
|
|
+ if (m.getOutBizNo() == null) continue;
|
|
|
|
|
+ BatchDetailEntity de = batchDetailMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchDetailEntity>()
|
|
|
|
|
+ .eq(BatchDetailEntity::getOutBizNo, m.getOutBizNo()));
|
|
|
|
|
+ if (de == null) continue;
|
|
|
|
|
+ if (m.getStatus() != null) de.setStatus(m.getStatus());
|
|
|
|
|
+ if (m.getErrorCode() != null) de.setErrorCode(m.getErrorCode());
|
|
|
|
|
+ if (m.getErrorMsg() != null) de.setErrorMsg(m.getErrorMsg());
|
|
|
|
|
+ batchDetailMapper.updateById(de);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return Map.of("out_batch_no", outBatchNo,
|
|
|
|
|
+ "status", response.getBatchStatus() != null ? response.getBatchStatus() : order.getStatus());
|
|
|
|
|
+ } catch (AlipayApiException e) {
|
|
|
|
|
+ throw new BusinessException(400, "查询批次失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** alipay.fund.batch.close — 主动关闭未支付批次 */
|
|
|
|
|
+ public Map<String, String> batchClose(String enterpriseId, String outBatchNo) {
|
|
|
|
|
+ BatchOrderEntity order = batchOrderMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
|
|
|
|
|
+ .eq(BatchOrderEntity::getOutBatchNo, outBatchNo));
|
|
|
|
|
+ if (order == null) throw new BusinessException(404, "批次不存在");
|
|
|
|
|
+ try {
|
|
|
|
|
+ AlipayFundBatchCloseModel model = new AlipayFundBatchCloseModel();
|
|
|
|
|
+ model.setBatchTransId(order.getBatchTransId());
|
|
|
|
|
+ model.setProductCode(BATCH_PRODUCT_CODE);
|
|
|
|
|
+ model.setBizScene(BATCH_BIZ_SCENE);
|
|
|
|
|
+ AlipayFundBatchCloseRequest request = new AlipayFundBatchCloseRequest();
|
|
|
|
|
+ request.setBizModel(model);
|
|
|
|
|
+ AlipayFundBatchCloseResponse response =
|
|
|
|
|
+ alipayClientFactory.getClient(enterpriseId, BIZ_TYPE).certificateExecute(request);
|
|
|
|
|
+ if (!response.isSuccess())
|
|
|
|
|
+ throw new BusinessException(400, "关闭批次失败: " + response.getMsg());
|
|
|
|
|
+ order.setStatus("DISUSE");
|
|
|
|
|
+ batchOrderMapper.updateById(order);
|
|
|
|
|
+ return Map.of("status", "DISUSE");
|
|
|
|
|
+ } catch (AlipayApiException e) {
|
|
|
|
|
+ throw new BusinessException(400, "关闭批次失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 列表 / 详情 / 导出 ====================
|
|
|
|
|
+
|
|
|
|
|
+ /** 授权列表(分页) */
|
|
|
|
|
+ public PageResult<BatchAuthorizeEntity> authorizeList(String enterpriseId, int pageNo, int pageSize) {
|
|
|
|
|
+ var w = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchAuthorizeEntity>()
|
|
|
|
|
+ .eq(enterpriseId != null && !enterpriseId.isBlank(), BatchAuthorizeEntity::getEnterpriseId, enterpriseId)
|
|
|
|
|
+ .orderByDesc(BatchAuthorizeEntity::getId);
|
|
|
|
|
+ var r = batchAuthorizeMapper.selectPage(new Page<>(pageNo, pageSize), w);
|
|
|
|
|
+ return PageResult.of(pageNo, pageSize, r.getTotal(), r.getRecords());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 批次列表(分页,状态/时间筛选)— 时间解析照抄 AccountService.parseDateTime 模式 */
|
|
|
|
|
+ public PageResult<BatchOrderEntity> batchList(String enterpriseId, String status,
|
|
|
|
|
+ String startTime, String endTime, int pageNo, int pageSize) {
|
|
|
|
|
+ var w = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
|
|
|
|
|
+ .eq(enterpriseId != null && !enterpriseId.isBlank(), BatchOrderEntity::getEnterpriseId, enterpriseId)
|
|
|
|
|
+ .eq(status != null && !status.isBlank(), BatchOrderEntity::getStatus, status)
|
|
|
|
|
+ .orderByDesc(BatchOrderEntity::getId);
|
|
|
|
|
+ if (startTime != null && !startTime.isBlank()) {
|
|
|
|
|
+ // "yyyy-MM-dd HH:mm" → "yyyy-MM-ddTHH:mm:00+08:00"(ISO 默认模式,无需自定义格式器)
|
|
|
|
|
+ w.ge(BatchOrderEntity::getCreatedTime,
|
|
|
|
|
+ OffsetDateTime.parse(startTime.replace(' ', 'T') + ":00+08:00"));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (endTime != null && !endTime.isBlank()) {
|
|
|
|
|
+ w.le(BatchOrderEntity::getCreatedTime,
|
|
|
|
|
+ OffsetDateTime.parse(endTime.replace(' ', 'T') + ":59+08:00"));
|
|
|
|
|
+ }
|
|
|
|
|
+ var r = batchOrderMapper.selectPage(new Page<>(pageNo, pageSize), w);
|
|
|
|
|
+ return PageResult.of(pageNo, pageSize, r.getTotal(), r.getRecords());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 批次详情 + 明细分页 */
|
|
|
|
|
+ public Map<String, Object> batchDetail(String outBatchNo, int pageNo, int pageSize) {
|
|
|
|
|
+ BatchOrderEntity order = batchOrderMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
|
|
|
|
|
+ .eq(BatchOrderEntity::getOutBatchNo, outBatchNo));
|
|
|
|
|
+ if (order == null) throw new BusinessException(404, "批次不存在");
|
|
|
|
|
+ Page<BatchDetailEntity> detailPage = batchDetailMapper.selectPage(new Page<>(pageNo, pageSize),
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchDetailEntity>()
|
|
|
|
|
+ .eq(BatchDetailEntity::getBatchId, order.getId())
|
|
|
|
|
+ .orderByAsc(BatchDetailEntity::getId));
|
|
|
|
|
+ return Map.of("order", order,
|
|
|
|
|
+ "details", PageResult.of(pageNo, pageSize, detailPage.getTotal(), detailPage.getRecords()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 批次导出 — 照抄 AccountService.transferExport 的组装写法(ExcelUtil.exportToExcel(listData, mappingDict)) */
|
|
|
|
|
+ public byte[] batchExport(String enterpriseId, String status, String startTime, String endTime) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ var w = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
|
|
|
|
|
+ .eq(enterpriseId != null && !enterpriseId.isBlank(), BatchOrderEntity::getEnterpriseId, enterpriseId)
|
|
|
|
|
+ .eq(status != null && !status.isBlank(), BatchOrderEntity::getStatus, status)
|
|
|
|
|
+ .orderByDesc(BatchOrderEntity::getId);
|
|
|
|
|
+ if (startTime != null && !startTime.isBlank()) {
|
|
|
|
|
+ w.ge(BatchOrderEntity::getCreatedTime,
|
|
|
|
|
+ OffsetDateTime.parse(startTime.replace(' ', 'T') + ":00+08:00"));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (endTime != null && !endTime.isBlank()) {
|
|
|
|
|
+ w.le(BatchOrderEntity::getCreatedTime,
|
|
|
|
|
+ OffsetDateTime.parse(endTime.replace(' ', 'T') + ":59+08:00"));
|
|
|
|
|
+ }
|
|
|
|
|
+ List<BatchOrderEntity> records = batchOrderMapper.selectList(w);
|
|
|
|
|
+ // 列: 序号/批次号/支付宝批次号/标题/金额(元)/笔数/状态/创建时间/错误信息
|
|
|
|
|
+ // 状态映射: INIT=受理中, SUCCESS=成功, DISUSE=已关闭, FAIL=失败
|
|
|
|
|
+ Map<String, String> statusMap = Map.of(
|
|
|
|
|
+ "INIT", "受理中",
|
|
|
|
|
+ "SUCCESS", "成功",
|
|
|
|
|
+ "DISUSE", "已关闭",
|
|
|
|
|
+ "FAIL", "失败");
|
|
|
|
|
+
|
|
|
|
|
+ List<Map<String, Object>> listData = new ArrayList<>();
|
|
|
|
|
+ DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
+ int seq = 1;
|
|
|
|
|
+ for (BatchOrderEntity r : records) {
|
|
|
|
|
+ Map<String, Object> row = new LinkedHashMap<>();
|
|
|
|
|
+ row.put("序号", seq++);
|
|
|
|
|
+ row.put("批次号", r.getOutBatchNo() != null ? r.getOutBatchNo() : "");
|
|
|
|
|
+ row.put("支付宝批次号", r.getBatchTransId() != null ? r.getBatchTransId() : "");
|
|
|
|
|
+ row.put("标题", r.getOrderTitle() != null ? r.getOrderTitle() : "");
|
|
|
|
|
+ row.put("金额(元)", r.getTotalAmount() != null ? r.getTotalAmount().toPlainString() : "0");
|
|
|
|
|
+ row.put("笔数", r.getTotalCount() != null ? r.getTotalCount() : 0);
|
|
|
|
|
+ row.put("状态", statusMap.getOrDefault(r.getStatus(), r.getStatus() != null ? r.getStatus() : ""));
|
|
|
|
|
+ row.put("创建时间", r.getCreatedTime() != null
|
|
|
|
|
+ ? r.getCreatedTime().atZoneSameInstant(ZoneId.of("Asia/Shanghai")).format(timeFmt) : "");
|
|
|
|
|
+ row.put("错误信息", r.getErrorMsg() != null ? r.getErrorMsg() : "");
|
|
|
|
|
+ listData.add(row);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> mappingDict = new LinkedHashMap<>();
|
|
|
|
|
+ mappingDict.put("序号", "序号");
|
|
|
|
|
+ mappingDict.put("批次号", "批次号");
|
|
|
|
|
+ mappingDict.put("支付宝批次号", "支付宝批次号");
|
|
|
|
|
+ mappingDict.put("标题", "标题");
|
|
|
|
|
+ mappingDict.put("金额(元)", "金额(元)");
|
|
|
|
|
+ mappingDict.put("笔数", "笔数");
|
|
|
|
|
+ mappingDict.put("状态", "状态");
|
|
|
|
|
+ mappingDict.put("创建时间", "创建时间");
|
|
|
|
|
+ mappingDict.put("错误信息", "错误信息");
|
|
|
|
|
+
|
|
|
|
|
+ log.info("导出批次报表: {} 条", listData.size());
|
|
|
|
|
+ return ExcelUtil.exportToExcel(listData, mappingDict);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("导出批次报表失败", e);
|
|
|
|
|
+ throw new RuntimeException("导出批次报表失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|