|
|
@@ -16,8 +16,11 @@ import com.payment.platform.common.response.PageResult;
|
|
|
import com.payment.platform.common.utils.RedisLockUtil;
|
|
|
import com.payment.platform.core.alipay.AlipayClientFactory;
|
|
|
import com.payment.platform.module.payment.facetoface.dto.*;
|
|
|
+import com.payment.platform.module.payment.facetoface.entity.F2fTradeRecordEntity;
|
|
|
import com.payment.platform.module.payment.facetoface.entity.FacetofaceOrderEntity;
|
|
|
+import com.payment.platform.module.payment.facetoface.enums.F2fTradeStatus;
|
|
|
import com.payment.platform.module.payment.facetoface.enums.FacetofaceOrderStatus;
|
|
|
+import com.payment.platform.module.payment.facetoface.mapper.F2fTradeRecordMapper;
|
|
|
import com.payment.platform.module.payment.facetoface.mapper.FacetofaceOrderMapper;
|
|
|
import com.payment.platform.module.payment.serviceprovider.enums.ServiceProviderBizType;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
@@ -39,6 +42,7 @@ import java.util.stream.Collectors;
|
|
|
public class FacetofaceService {
|
|
|
|
|
|
private final FacetofaceOrderMapper mapper;
|
|
|
+ private final F2fTradeRecordMapper tradeRecordMapper;
|
|
|
private final AlipayClientFactory alipayClientFactory;
|
|
|
private final RedisLockUtil redisLockUtil;
|
|
|
|
|
|
@@ -403,6 +407,21 @@ public class FacetofaceService {
|
|
|
order.getAppAuthToken() != null ? order.getAppAuthToken().length() : 0);
|
|
|
|
|
|
String outTradeNo = "F2F" + System.currentTimeMillis() + String.format("%04d", (int) (Math.random() * 10000));
|
|
|
+
|
|
|
+ // 先保存交易记录(状态 WAIT_BUYER_PAY)
|
|
|
+ F2fTradeRecordEntity tradeRecord = new F2fTradeRecordEntity();
|
|
|
+ tradeRecord.setOutTradeNo(outTradeNo);
|
|
|
+ tradeRecord.setTotalAmount(dto.getTotalAmount());
|
|
|
+ tradeRecord.setSubject(StrUtil.isNotBlank(dto.getSubject()) ? dto.getSubject() : "当面付收款");
|
|
|
+ tradeRecord.setTradeStatus(F2fTradeStatus.WAIT_BUYER_PAY.getValue());
|
|
|
+ tradeRecord.setEnterpriseId(dto.getEnterpriseId());
|
|
|
+ tradeRecord.setGmtCreate(OffsetDateTime.now());
|
|
|
+ tradeRecord.setNextQueryTime(OffsetDateTime.now().plusMinutes(1));
|
|
|
+ tradeRecord.setQueryCount(0);
|
|
|
+ tradeRecord.setStatus("0");
|
|
|
+ tradeRecordMapper.insert(tradeRecord);
|
|
|
+ log.info("[收款] 交易记录已保存: out_trade_no={}, status=WAIT_BUYER_PAY", outTradeNo);
|
|
|
+
|
|
|
try {
|
|
|
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
|
|
model.setOutTradeNo(outTradeNo);
|
|
|
@@ -428,27 +447,51 @@ public class FacetofaceService {
|
|
|
response.getCode(), response.getMsg(), response.getSubCode(), response.getSubMsg());
|
|
|
|
|
|
if (!response.isSuccess()) {
|
|
|
+ // 预下单失败 — 标记交易为 CLOSED
|
|
|
+ tradeRecord.setTradeStatus(F2fTradeStatus.TRADE_CLOSED.getValue());
|
|
|
+ tradeRecord.setNextQueryTime(null);
|
|
|
+ tradeRecord.setDescription("precreate失败: " +
|
|
|
+ (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
|
|
|
+ tradeRecordMapper.updateById(tradeRecord);
|
|
|
throw new BusinessException(400, "生成收款码失败: " +
|
|
|
(response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
|
|
|
}
|
|
|
|
|
|
log.info("[收款] 收款码生成成功: out_trade_no={}, qr_code={}", outTradeNo, response.getQrCode());
|
|
|
|
|
|
+ // 更新收款码URL
|
|
|
+ tradeRecord.setQrCode(response.getQrCode());
|
|
|
+ tradeRecordMapper.updateById(tradeRecord);
|
|
|
+
|
|
|
F2fTradeVO vo = new F2fTradeVO();
|
|
|
vo.setOutTradeNo(outTradeNo);
|
|
|
vo.setQrCode(response.getQrCode());
|
|
|
+ vo.setTradeStatus(F2fTradeStatus.WAIT_BUYER_PAY.getValue());
|
|
|
return vo;
|
|
|
|
|
|
} catch (AlipayApiException ex) {
|
|
|
+ // 网络异常 — 标记交易为 CLOSED
|
|
|
+ tradeRecord.setTradeStatus(F2fTradeStatus.TRADE_CLOSED.getValue());
|
|
|
+ tradeRecord.setNextQueryTime(null);
|
|
|
+ tradeRecord.setDescription("precreate异常: " + ex.getMessage());
|
|
|
+ tradeRecordMapper.updateById(tradeRecord);
|
|
|
log.error("[收款] AlipayApiException: {}", ex.getMessage(), ex);
|
|
|
throw new BusinessException(400, "生成收款码失败: " + ex.getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /** 查询交易状态 — 调用 alipay.trade.query */
|
|
|
+ /** 查询交易状态 — 调用 alipay.trade.query,更新本地记录 */
|
|
|
public F2fTradeVO queryTrade(F2fTradeQueryDTO dto) {
|
|
|
FacetofaceOrderEntity order = getSuccessOrder(dto.getEnterprise_id());
|
|
|
|
|
|
+ // 先查本地记录
|
|
|
+ F2fTradeRecordEntity record = tradeRecordMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<F2fTradeRecordEntity>()
|
|
|
+ .eq(F2fTradeRecordEntity::getOutTradeNo, dto.getOut_trade_no()));
|
|
|
+ if (record == null) {
|
|
|
+ throw new BusinessException(404, "未找到该交易记录");
|
|
|
+ }
|
|
|
+
|
|
|
try {
|
|
|
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
|
|
|
model.setOutTradeNo(dto.getOut_trade_no());
|
|
|
@@ -464,6 +507,28 @@ public class FacetofaceService {
|
|
|
(response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
|
|
|
}
|
|
|
|
|
|
+ // 更新本地记录
|
|
|
+ String alipayTradeStatus = response.getTradeStatus();
|
|
|
+ record.setTradeStatus(alipayTradeStatus != null ? alipayTradeStatus : record.getTradeStatus());
|
|
|
+ record.setTradeNo(response.getTradeNo());
|
|
|
+ record.setBuyerId(response.getBuyerUserId());
|
|
|
+ record.setBuyerLogonId(response.getBuyerLogonId());
|
|
|
+ if (response.getSendPayDate() != null) {
|
|
|
+ record.setGmtPayment(response.getSendPayDate().toInstant().atOffset(java.time.ZoneOffset.ofHours(8)));
|
|
|
+ }
|
|
|
+ // 终态不继续轮询
|
|
|
+ if (alipayTradeStatus != null &&
|
|
|
+ (alipayTradeStatus.equals(F2fTradeStatus.TRADE_SUCCESS.getValue())
|
|
|
+ || alipayTradeStatus.equals(F2fTradeStatus.TRADE_FINISHED.getValue())
|
|
|
+ || alipayTradeStatus.equals(F2fTradeStatus.TRADE_CLOSED.getValue()))) {
|
|
|
+ record.setNextQueryTime(null);
|
|
|
+ } else {
|
|
|
+ record.setNextQueryTime(OffsetDateTime.now().plusMinutes(1));
|
|
|
+ }
|
|
|
+ record.setLastQueryTime(OffsetDateTime.now());
|
|
|
+ record.setQueryCount(record.getQueryCount() != null ? record.getQueryCount() + 1 : 1);
|
|
|
+ tradeRecordMapper.updateById(record);
|
|
|
+
|
|
|
F2fTradeVO vo = new F2fTradeVO();
|
|
|
vo.setOutTradeNo(dto.getOut_trade_no());
|
|
|
vo.setTradeNo(response.getTradeNo());
|
|
|
@@ -498,6 +563,78 @@ public class FacetofaceService {
|
|
|
return order;
|
|
|
}
|
|
|
|
|
|
+ // ========== 定时轮询 ==========
|
|
|
+
|
|
|
+ /** 获取待轮询的交易记录 */
|
|
|
+ public List<F2fTradeRecordEntity> getPendingTrades() {
|
|
|
+ return tradeRecordMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<F2fTradeRecordEntity>()
|
|
|
+ .le(F2fTradeRecordEntity::getNextQueryTime, OffsetDateTime.now())
|
|
|
+ .eq(F2fTradeRecordEntity::getTradeStatus, F2fTradeStatus.WAIT_BUYER_PAY.getValue())
|
|
|
+ .last("LIMIT 100"));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 内部轮询方法 — 仿照 queryTrade 逻辑,不抛异常 */
|
|
|
+ public void doQueryTrade(F2fTradeRecordEntity record) {
|
|
|
+ FacetofaceOrderEntity order;
|
|
|
+ try {
|
|
|
+ order = getSuccessOrder(record.getEnterpriseId());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[收款轮询] 获取商户授权失败: out_trade_no={}, error={}", record.getOutTradeNo(), e.getMessage());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ AlipayTradeQueryModel model = new AlipayTradeQueryModel();
|
|
|
+ model.setOutTradeNo(record.getOutTradeNo());
|
|
|
+
|
|
|
+ AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
|
|
|
+ request.setBizModel(model);
|
|
|
+ request.putOtherTextParam("app_auth_token", order.getAppAuthToken());
|
|
|
+
|
|
|
+ AlipayTradeQueryResponse response = alipayClientFactory.getClient(
|
|
|
+ record.getEnterpriseId(), ServiceProviderBizType.FACETOFACE_NOTIFY.getValue()).execute(request);
|
|
|
+
|
|
|
+ if (!response.isSuccess()) {
|
|
|
+ log.warn("[收款轮询] 查询失败: out_trade_no={}, code={}, msg={}",
|
|
|
+ record.getOutTradeNo(), response.getCode(), response.getSubMsg());
|
|
|
+ record.setNextQueryTime(OffsetDateTime.now().plusMinutes(5));
|
|
|
+ record.setLastQueryTime(OffsetDateTime.now());
|
|
|
+ record.setQueryCount(record.getQueryCount() != null ? record.getQueryCount() + 1 : 1);
|
|
|
+ tradeRecordMapper.updateById(record);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String alipayStatus = response.getTradeStatus();
|
|
|
+ log.info("[收款轮询] 查询成功: out_trade_no={}, trade_status={}, trade_no={}",
|
|
|
+ record.getOutTradeNo(), alipayStatus, response.getTradeNo());
|
|
|
+
|
|
|
+ record.setTradeStatus(alipayStatus != null ? alipayStatus : record.getTradeStatus());
|
|
|
+ record.setTradeNo(response.getTradeNo());
|
|
|
+ record.setBuyerId(response.getBuyerUserId());
|
|
|
+ record.setBuyerLogonId(response.getBuyerLogonId());
|
|
|
+ if (response.getSendPayDate() != null) {
|
|
|
+ record.setGmtPayment(response.getSendPayDate().toInstant().atOffset(java.time.ZoneOffset.ofHours(8)));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 终态停止轮询
|
|
|
+ if (alipayStatus != null &&
|
|
|
+ (alipayStatus.equals(F2fTradeStatus.TRADE_SUCCESS.getValue())
|
|
|
+ || alipayStatus.equals(F2fTradeStatus.TRADE_FINISHED.getValue())
|
|
|
+ || alipayStatus.equals(F2fTradeStatus.TRADE_CLOSED.getValue()))) {
|
|
|
+ record.setNextQueryTime(null);
|
|
|
+ } else {
|
|
|
+ record.setNextQueryTime(OffsetDateTime.now().plusMinutes(1));
|
|
|
+ }
|
|
|
+ record.setLastQueryTime(OffsetDateTime.now());
|
|
|
+ record.setQueryCount(record.getQueryCount() != null ? record.getQueryCount() + 1 : 1);
|
|
|
+ tradeRecordMapper.updateById(record);
|
|
|
+
|
|
|
+ } catch (AlipayApiException ex) {
|
|
|
+ log.error("[收款轮询] 异常: out_trade_no={}, error={}", record.getOutTradeNo(), ex.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// ========== private ==========
|
|
|
|
|
|
private FacetofaceOrderEntity requireOrder(Long id) {
|