同步平台业务模块能力
This commit is contained in:
@@ -6,10 +6,10 @@ import com.xspaceagi.pay.application.support.PayOrderBillNotifySupport;
|
||||
import com.xspaceagi.pay.application.support.PayOrderBizNotifySupport;
|
||||
import com.xspaceagi.pay.application.support.PayOrderGatewayQueryMerger;
|
||||
import com.xspaceagi.pay.application.support.PayOrderGatewaySyncFailureSupport;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentScanGateway;
|
||||
import com.xspaceagi.pay.application.support.PayOrderStatusQuerySupport;
|
||||
import com.xspaceagi.pay.domain.model.PayOrderModel;
|
||||
import com.xspaceagi.pay.domain.repository.PayOrderRepository;
|
||||
import com.xspaceagi.pay.sdk.dto.ScanOrderStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.enums.PaymentStatus;
|
||||
import com.xspaceagi.pay.spec.enums.PayBizNotifyStatus;
|
||||
import com.xspaceagi.pay.spec.exception.PayGatewayClientException;
|
||||
@@ -44,7 +44,7 @@ public class PayOrderGatewayReconcileRunner {
|
||||
public static final int LOOKBACK_DAYS = 7;
|
||||
|
||||
private final PayOrderRepository payOrderRepository;
|
||||
private final PaymentScanGateway paymentScanGateway;
|
||||
private final PayOrderStatusQuerySupport payOrderStatusQuerySupport;
|
||||
private final PayGatewayOutboundExecutor payGatewayOutboundExecutor;
|
||||
private final IBillRpcService billRpcService;
|
||||
|
||||
@@ -116,9 +116,9 @@ public class PayOrderGatewayReconcileRunner {
|
||||
e.getMessage());
|
||||
return false;
|
||||
}
|
||||
ScanOrderStatusQueryResponse last;
|
||||
PaymentStatusQueryResponse last;
|
||||
try {
|
||||
last = paymentScanGateway.queryOrderStatus(outbound, row.getGatewayPaymentOrderNo(), true);
|
||||
last = payOrderStatusQuerySupport.queryOrderStatus(outbound, row, true);
|
||||
} catch (PayGatewayClientException e) {
|
||||
log.warn(
|
||||
"[pay-reconcile] query failed payOrderId={} tenantId={} gatewayNo={} gatewayCode={} gatewayMessage={}",
|
||||
@@ -152,7 +152,7 @@ public class PayOrderGatewayReconcileRunner {
|
||||
}
|
||||
|
||||
private boolean tryNotifyTerminalAndMarkNotified(
|
||||
PayOrderModel row, ScanOrderStatusQueryResponse last, PayBizNotifyStatus expectedCurrent) {
|
||||
PayOrderModel row, PaymentStatusQueryResponse last, PayBizNotifyStatus expectedCurrent) {
|
||||
if (!PayOrderBillNotifySupport.notifyBillPaymentCallback(billRpcService, row, last, false)) {
|
||||
log.warn("[pay-reconcile] bill notify failed payOrderId={}, will retry next tick", row.getId());
|
||||
return false;
|
||||
|
||||
@@ -3,10 +3,10 @@ package com.xspaceagi.pay.application.schedule;
|
||||
import com.xspaceagi.bill.sdk.rpc.IBillRpcService;
|
||||
import com.xspaceagi.pay.application.support.PayOrderBillNotifySupport;
|
||||
import com.xspaceagi.pay.application.support.PayOrderGatewayQueryMerger;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentScanGateway;
|
||||
import com.xspaceagi.pay.application.support.PayOrderStatusQuerySupport;
|
||||
import com.xspaceagi.pay.domain.model.PayOrderModel;
|
||||
import com.xspaceagi.pay.domain.repository.PayOrderRepository;
|
||||
import com.xspaceagi.pay.sdk.dto.ScanOrderStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryResponse;
|
||||
import com.xspaceagi.pay.spec.enums.PayBizNotifyStatus;
|
||||
import com.xspaceagi.pay.sdk.enums.PaymentStatus;
|
||||
import com.xspaceagi.pay.spec.enums.PayOrderGatewaySyncStatus;
|
||||
@@ -32,19 +32,19 @@ public class PayOrderPollRunner {
|
||||
public static final String TASK_BEAN_ID = "payOrderPollTaskExecuteService";
|
||||
|
||||
private final PayOrderRepository payOrderRepository;
|
||||
private final PaymentScanGateway paymentScanGateway;
|
||||
private final PayOrderStatusQuerySupport payOrderStatusQuerySupport;
|
||||
private final PayGatewayOutboundExecutor payGatewayOutboundExecutor;
|
||||
private final IBillRpcService billRpcService;
|
||||
private final ScheduleTaskApiService scheduleTaskApiService;
|
||||
|
||||
public PayOrderPollRunner(
|
||||
PayOrderRepository payOrderRepository,
|
||||
PaymentScanGateway paymentScanGateway,
|
||||
PayOrderStatusQuerySupport payOrderStatusQuerySupport,
|
||||
PayGatewayOutboundExecutor payGatewayOutboundExecutor,
|
||||
IBillRpcService billRpcService,
|
||||
ScheduleTaskApiService scheduleTaskApiService) {
|
||||
this.payOrderRepository = payOrderRepository;
|
||||
this.paymentScanGateway = paymentScanGateway;
|
||||
this.payOrderStatusQuerySupport = payOrderStatusQuerySupport;
|
||||
this.payGatewayOutboundExecutor = payGatewayOutboundExecutor;
|
||||
this.billRpcService = billRpcService;
|
||||
this.scheduleTaskApiService = scheduleTaskApiService;
|
||||
@@ -98,7 +98,7 @@ public class PayOrderPollRunner {
|
||||
return true;
|
||||
}
|
||||
|
||||
ScanOrderStatusQueryResponse last = null;
|
||||
PaymentStatusQueryResponse last = null;
|
||||
PayGatewayOutbound outbound;
|
||||
try {
|
||||
outbound = payGatewayOutboundExecutor.resolveCached(row.getTenantId());
|
||||
@@ -107,7 +107,7 @@ public class PayOrderPollRunner {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
last = paymentScanGateway.queryOrderStatus(outbound, row.getGatewayPaymentOrderNo(), true);
|
||||
last = payOrderStatusQuerySupport.queryOrderStatus(outbound, row, true);
|
||||
PayOrderGatewayQueryMerger.mergeIntoRow(row, last);
|
||||
payOrderRepository.save(row);
|
||||
|
||||
@@ -136,7 +136,7 @@ public class PayOrderPollRunner {
|
||||
/**
|
||||
* 先通知业务,成功后再 CAS 为 NOTIFIED,避免 NOTIFIED 但业务未收到回调。
|
||||
*/
|
||||
private boolean tryNotifyAndMarkNotified(PayOrderModel row, ScanOrderStatusQueryResponse last, Long payOrderId) {
|
||||
private boolean tryNotifyAndMarkNotified(PayOrderModel row, PaymentStatusQueryResponse last, Long payOrderId) {
|
||||
if (!PayOrderBillNotifySupport.notifyBillPaymentCallback(billRpcService, row, last, false)) {
|
||||
log.warn("[pay-poll] bill notify failed orderId={}, will retry", payOrderId);
|
||||
return false;
|
||||
@@ -153,7 +153,7 @@ public class PayOrderPollRunner {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean tryNotifyTimeoutAndMark(PayOrderModel row, ScanOrderStatusQueryResponse last, Long payOrderId) {
|
||||
private boolean tryNotifyTimeoutAndMark(PayOrderModel row, PaymentStatusQueryResponse last, Long payOrderId) {
|
||||
if (!PayOrderBillNotifySupport.notifyBillPaymentCallback(billRpcService, row, last, true)) {
|
||||
log.warn("[pay-poll] bill notify timeout failed orderId={}, will retry", payOrderId);
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.pay.application.service;
|
||||
|
||||
import com.xspaceagi.pay.sdk.dto.FileEntryUploadResponse;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
public interface FileEntryApplicationService {
|
||||
|
||||
FileEntryUploadResponse uploadMerchantOnboardingImage(MultipartFile file, String replaceFileKey);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.xspaceagi.pay.application.service.impl;
|
||||
|
||||
import com.xspaceagi.pay.application.service.FileEntryApplicationService;
|
||||
import com.xspaceagi.pay.application.support.PayGatewayOutboundExecutor;
|
||||
import com.xspaceagi.pay.domain.gateway.FileEntryGateway;
|
||||
import com.xspaceagi.pay.sdk.dto.FileEntryUploadResponse;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FileEntryApplicationServiceImpl implements FileEntryApplicationService {
|
||||
|
||||
@Resource
|
||||
private FileEntryGateway payFileEntryGateway;
|
||||
|
||||
@Resource
|
||||
private PayGatewayOutboundExecutor payGatewayOutboundExecutor;
|
||||
|
||||
@Override
|
||||
public FileEntryUploadResponse uploadMerchantOnboardingImage(MultipartFile file, String replaceFileKey) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new IllegalArgumentException("请选择文件");
|
||||
}
|
||||
final byte[] fileBytes;
|
||||
try {
|
||||
fileBytes = file.getBytes();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("read upload file failed", e);
|
||||
}
|
||||
String filename = file.getOriginalFilename();
|
||||
String contentType = file.getContentType();
|
||||
return payGatewayOutboundExecutor.invoke(
|
||||
resolveTenantId(),
|
||||
outbound -> payFileEntryGateway.uploadMerchantOnboardingImage(
|
||||
outbound, fileBytes, filename, contentType, replaceFileKey));
|
||||
}
|
||||
|
||||
private static long resolveTenantId() {
|
||||
RequestContext<?> ctx = RequestContext.get();
|
||||
if (ctx != null && ctx.getTenantId() != null) {
|
||||
return ctx.getTenantId();
|
||||
}
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "无法确定租户:请登录后重试");
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,37 @@ package com.xspaceagi.pay.application.service.impl;
|
||||
import com.xspaceagi.bill.sdk.rpc.IBillRpcService;
|
||||
import com.xspaceagi.pay.application.schedule.PayOrderPollRunner;
|
||||
import com.xspaceagi.pay.application.support.PayGatewayOutboundExecutor;
|
||||
import com.xspaceagi.pay.application.support.PayOrderExtSupport;
|
||||
import com.xspaceagi.pay.application.support.PayOrderGatewayQueryMerger;
|
||||
import com.xspaceagi.pay.application.support.PayOrderGatewaySyncFailureSupport;
|
||||
import com.xspaceagi.pay.application.support.PayOrderSettlementSyncService;
|
||||
import com.xspaceagi.pay.application.support.PayOrderStatusQuerySupport;
|
||||
import com.xspaceagi.pay.spec.PayOrderScheduleConstants;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentAppGateway;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentH5Gateway;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentMiniPayGateway;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentScanGateway;
|
||||
import com.xspaceagi.pay.domain.model.PayOrderModel;
|
||||
import com.xspaceagi.pay.domain.repository.PayOrderRepository;
|
||||
import com.xspaceagi.pay.sdk.dto.AppOrderRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.AppTransactionRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentOrderCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.H5OrderRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.H5TransactionRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.MiniPayOrderRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.MiniPayTransactionRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.OrderAndTransactionCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.ScanOrderCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.ScanOrderStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.service.IPaymentRpcService;
|
||||
import com.xspaceagi.pay.spec.enums.PayBizNotifyStatus;
|
||||
import com.xspaceagi.pay.sdk.dto.OrderCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.enums.PayChannel;
|
||||
import com.xspaceagi.pay.sdk.enums.PayClientScene;
|
||||
import com.xspaceagi.pay.sdk.enums.PayMode;
|
||||
import com.xspaceagi.pay.sdk.enums.PaymentStatus;
|
||||
import com.xspaceagi.pay.spec.enums.PayOrderGatewaySyncStatus;
|
||||
import com.xspaceagi.pay.spec.exception.PayGatewayClientException;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
@@ -30,7 +45,9 @@ import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -43,6 +60,18 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
@Resource
|
||||
private PaymentScanGateway paymentScanGateway;
|
||||
|
||||
@Resource
|
||||
private PaymentMiniPayGateway paymentMiniPayGateway;
|
||||
|
||||
@Resource
|
||||
private PaymentH5Gateway paymentH5Gateway;
|
||||
|
||||
@Resource
|
||||
private PaymentAppGateway paymentAppGateway;
|
||||
|
||||
@Resource
|
||||
private PayOrderStatusQuerySupport payOrderStatusQuerySupport;
|
||||
|
||||
@Resource
|
||||
private PayGatewayOutboundExecutor payGatewayOutboundExecutor;
|
||||
|
||||
@@ -55,52 +84,189 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
@Resource
|
||||
private PayOrderSettlementSyncService payOrderSettlementSyncService;
|
||||
|
||||
public PaymentOrderCreateResponse createOrderForScan(ScanOrderCreateRequest request) {
|
||||
Assert.hasText(request.getBizOrderNo(), "bizOrderNo cannot be left blank");
|
||||
Assert.notNull(request.getOrderAmount(), "orderAmount cannot be left blank");
|
||||
Assert.isTrue(request.getOrderAmount() > 0, "orderAmount must be greater than 0");
|
||||
Assert.hasText(request.getSubject(), "subject cannot be left blank");
|
||||
|
||||
long tenantId = resolveTenantId();
|
||||
PayOrderModel existing =
|
||||
payOrderRepository.findByTenantIdAndBizOrderNo(tenantId, request.getBizOrderNo()).orElse(null);
|
||||
if (existing != null) {
|
||||
return resumeExistingOrder(existing);
|
||||
}
|
||||
|
||||
PayOrderModel row = PayOrderModel.builder()
|
||||
.tenantId(tenantId)
|
||||
.bizOrderNo(request.getBizOrderNo())
|
||||
.bizScene(null)
|
||||
.orderAmount(request.getOrderAmount())
|
||||
.subject(request.getSubject())
|
||||
.ext(request.getExt())
|
||||
.payMode(PayMode.scan)
|
||||
.platformFee(0L)
|
||||
.providerFee(0L)
|
||||
.netAmount(0L)
|
||||
.gatewaySyncStatus(PayOrderGatewaySyncStatus.PENDING)
|
||||
.build();
|
||||
try {
|
||||
row = payOrderRepository.save(row);
|
||||
} catch (DuplicateKeyException e) {
|
||||
PayOrderModel race = payOrderRepository.findByTenantIdAndBizOrderNo(tenantId, request.getBizOrderNo()).orElse(null);
|
||||
if (race == null) {
|
||||
throw new BizException(ErrorCodeEnum.SYS_ERROR.getCode(), "创建订单冲突,请重试");
|
||||
}
|
||||
return reconcileAfterDuplicate(race);
|
||||
}
|
||||
return callGatewayAndFinish(row);
|
||||
@Override
|
||||
public OrderCreateResponse createOrderForScan(ScanOrderCreateRequest request) {
|
||||
return createPayOrder(
|
||||
PayMode.scan,
|
||||
request.getBizOrderNo(),
|
||||
request.getOrderAmount(),
|
||||
request.getSubject(),
|
||||
request.getExt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderCreateResponse createOrderForMiniPay(MiniPayOrderRpcCreateRequest request) {
|
||||
return createPayOrder(
|
||||
PayMode.minipay,
|
||||
request.getBizOrderNo(),
|
||||
request.getOrderAmount(),
|
||||
request.getSubject(),
|
||||
request.getExt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderCreateResponse createOrderForH5(H5OrderRpcCreateRequest request) {
|
||||
return createPayOrder(
|
||||
PayMode.h5,
|
||||
request.getBizOrderNo(),
|
||||
request.getOrderAmount(),
|
||||
request.getSubject(),
|
||||
request.getExt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderCreateResponse createOrderForApp(AppOrderRpcCreateRequest request) {
|
||||
return createPayOrder(
|
||||
PayMode.app,
|
||||
request.getBizOrderNo(),
|
||||
request.getOrderAmount(),
|
||||
request.getSubject(),
|
||||
request.getExt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderAndTransactionCreateResponse createMiniPayTransaction(MiniPayTransactionRpcCreateRequest request) {
|
||||
Assert.hasText(request.getBizOrderNo(), "bizOrderNo cannot be left blank");
|
||||
Assert.notNull(request.getPayChannel(), "payChannel cannot be null");
|
||||
validateMiniPayChannelParams(request);
|
||||
|
||||
long tenantId = resolveTenantId();
|
||||
PayOrderModel row = payOrderRepository
|
||||
.findByTenantIdAndBizOrderNo(tenantId, request.getBizOrderNo().trim())
|
||||
.orElse(null);
|
||||
if (row == null) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.pay_order_not_found);
|
||||
}
|
||||
if (row.getPayMode() != PayMode.minipay) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "非小程序支付单");
|
||||
}
|
||||
if (row.getGatewaySyncStatus() != PayOrderGatewaySyncStatus.SUCCESS) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "支付单尚未同步网关,无法调起支付");
|
||||
}
|
||||
|
||||
try {
|
||||
OrderAndTransactionCreateResponse resp = payGatewayOutboundExecutor.invoke(
|
||||
tenantId,
|
||||
outbound -> paymentMiniPayGateway.createTransaction(
|
||||
outbound,
|
||||
row.getGatewayPaymentOrderNo(),
|
||||
request.getPayChannel(),
|
||||
request.getSubAppid(),
|
||||
request.getOpenId(),
|
||||
request.getBuyerId(),
|
||||
request.getClientIp(),
|
||||
request.getPayClientScene()));
|
||||
mergeTransactionResponseIntoRow(row, resp);
|
||||
if (request.getPayClientScene() != null) {
|
||||
row.setExt(PayOrderExtSupport.mergePayClientScene(row.getExt(), request.getPayClientScene()));
|
||||
}
|
||||
if (row.getBizNotifyStatus() != PayBizNotifyStatus.POLLING) {
|
||||
row.setBizNotifyStatus(PayBizNotifyStatus.POLLING);
|
||||
}
|
||||
payOrderRepository.save(row);
|
||||
// 重试调起时若调度任务已结束,同 taskId 的 start 会将其拉回 CREATE 并继续轮询
|
||||
payOrderPayPollRunner.startPoll(row.getId(), row.getTenantId());
|
||||
return resp;
|
||||
} catch (PayGatewayClientException e) {
|
||||
throw PayGatewayExceptionMapper.toBizException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderAndTransactionCreateResponse createH5Transaction(H5TransactionRpcCreateRequest request) {
|
||||
Assert.hasText(request.getBizOrderNo(), "bizOrderNo cannot be left blank");
|
||||
Assert.notNull(request.getPayChannel(), "payChannel cannot be null");
|
||||
Assert.hasText(request.getFrontNotifyUrl(), "frontNotifyUrl cannot be left blank");
|
||||
String frontNotifyUrl = validateFrontNotifyUrl(request.getFrontNotifyUrl().trim());
|
||||
|
||||
long tenantId = resolveTenantId();
|
||||
PayOrderModel row = payOrderRepository
|
||||
.findByTenantIdAndBizOrderNo(tenantId, request.getBizOrderNo().trim())
|
||||
.orElse(null);
|
||||
if (row == null) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.pay_order_not_found);
|
||||
}
|
||||
if (row.getPayMode() != PayMode.h5) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "非H5支付单");
|
||||
}
|
||||
if (row.getGatewaySyncStatus() != PayOrderGatewaySyncStatus.SUCCESS) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "支付单尚未同步网关,无法调起支付");
|
||||
}
|
||||
|
||||
try {
|
||||
OrderAndTransactionCreateResponse resp = payGatewayOutboundExecutor.invoke(
|
||||
tenantId,
|
||||
outbound -> paymentH5Gateway.createTransaction(
|
||||
outbound,
|
||||
row.getGatewayPaymentOrderNo(),
|
||||
request.getPayChannel(),
|
||||
request.getClientIp(),
|
||||
frontNotifyUrl));
|
||||
mergeTransactionResponseIntoRow(row, resp);
|
||||
row.setExt(PayOrderExtSupport.mergePayClientScene(row.getExt(), PayClientScene.H5_WEB));
|
||||
if (row.getBizNotifyStatus() != PayBizNotifyStatus.POLLING) {
|
||||
row.setBizNotifyStatus(PayBizNotifyStatus.POLLING);
|
||||
}
|
||||
payOrderRepository.save(row);
|
||||
payOrderPayPollRunner.startPoll(row.getId(), row.getTenantId());
|
||||
return resp;
|
||||
} catch (PayGatewayClientException e) {
|
||||
throw PayGatewayExceptionMapper.toBizException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderAndTransactionCreateResponse createAppTransaction(AppTransactionRpcCreateRequest request) {
|
||||
Assert.hasText(request.getBizOrderNo(), "bizOrderNo cannot be left blank");
|
||||
Assert.notNull(request.getPayChannel(), "payChannel cannot be null");
|
||||
validateAppPayChannel(request.getPayChannel());
|
||||
|
||||
long tenantId = resolveTenantId();
|
||||
PayOrderModel row = payOrderRepository
|
||||
.findByTenantIdAndBizOrderNo(tenantId, request.getBizOrderNo().trim())
|
||||
.orElse(null);
|
||||
if (row == null) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.pay_order_not_found);
|
||||
}
|
||||
if (row.getPayMode() != PayMode.app) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "非 App 原生支付单");
|
||||
}
|
||||
if (row.getGatewaySyncStatus() != PayOrderGatewaySyncStatus.SUCCESS) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "支付单尚未同步网关,无法调起支付");
|
||||
}
|
||||
|
||||
try {
|
||||
OrderAndTransactionCreateResponse resp = payGatewayOutboundExecutor.invoke(
|
||||
tenantId,
|
||||
outbound -> paymentAppGateway.createTransaction(
|
||||
outbound,
|
||||
row.getGatewayPaymentOrderNo(),
|
||||
request.getPayChannel(),
|
||||
request.getClientIp(),
|
||||
request.getPayClientScene() != null
|
||||
? request.getPayClientScene()
|
||||
: PayClientScene.NATIVE_APP));
|
||||
mergeTransactionResponseIntoRow(row, resp);
|
||||
row.setExt(PayOrderExtSupport.mergePayClientScene(row.getExt(), PayClientScene.NATIVE_APP));
|
||||
if (row.getBizNotifyStatus() != PayBizNotifyStatus.POLLING) {
|
||||
row.setBizNotifyStatus(PayBizNotifyStatus.POLLING);
|
||||
}
|
||||
payOrderRepository.save(row);
|
||||
payOrderPayPollRunner.startPoll(row.getId(), row.getTenantId());
|
||||
return resp;
|
||||
} catch (PayGatewayClientException e) {
|
||||
throw PayGatewayExceptionMapper.toBizException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CashierSessionCreateResponse createCashierSession(CashierSessionCreateRequest request) {
|
||||
Assert.notNull(request, "request cannot be null");
|
||||
Assert.hasText(request.getGatewayPaymentOrderNo(), "gatewayPaymentOrderNo cannot be left blank");
|
||||
|
||||
long tenantId = resolveTenantId();
|
||||
PayOrderModel row;
|
||||
String gatewayNo = request.getGatewayPaymentOrderNo().trim();
|
||||
row = payOrderRepository.findByTenantIdAndGatewayPaymentOrderNo(tenantId, gatewayNo).orElse(null);
|
||||
PayOrderModel row = payOrderRepository.findByTenantIdAndGatewayPaymentOrderNo(tenantId, gatewayNo).orElse(null);
|
||||
if (row == null) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.pay_order_not_found);
|
||||
}
|
||||
@@ -117,7 +283,8 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
request.getBizRedirectUrl()));
|
||||
}
|
||||
|
||||
public ScanOrderStatusQueryResponse queryStatus(PaymentStatusQueryRequest request) {
|
||||
@Override
|
||||
public PaymentStatusQueryResponse queryStatus(PaymentStatusQueryRequest request) {
|
||||
Assert.hasText(request.getGatewayPaymentOrderNo(), "gatewayPaymentOrderNo cannot be left blank");
|
||||
|
||||
long tenantId = resolveTenantId();
|
||||
@@ -127,9 +294,9 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
if (row == null) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.pay_order_not_found);
|
||||
}
|
||||
ScanOrderStatusQueryResponse resp = payGatewayOutboundExecutor.invoke(
|
||||
PaymentStatusQueryResponse resp = payGatewayOutboundExecutor.invoke(
|
||||
tenantId,
|
||||
outbound -> paymentScanGateway.queryOrderStatus(outbound, request.getGatewayPaymentOrderNo(), true));
|
||||
outbound -> payOrderStatusQuerySupport.queryOrderStatus(outbound, row, true));
|
||||
PayOrderGatewayQueryMerger.mergeIntoRow(row, resp);
|
||||
payOrderRepository.save(row);
|
||||
return resp;
|
||||
@@ -141,36 +308,101 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
return payOrderSettlementSyncService.syncSettlementForBizOrderNo(tenantId, bizOrderNo);
|
||||
}
|
||||
|
||||
private PaymentOrderCreateResponse reconcileAfterDuplicate(PayOrderModel race) {
|
||||
return resumeExistingOrder(race);
|
||||
private OrderCreateResponse createPayOrder(
|
||||
PayMode payMode, String bizOrderNo, Long orderAmount, String subject, String ext) {
|
||||
Assert.hasText(bizOrderNo, "bizOrderNo cannot be left blank");
|
||||
Assert.notNull(orderAmount, "orderAmount cannot be left blank");
|
||||
Assert.isTrue(orderAmount > 0, "orderAmount must be greater than 0");
|
||||
Assert.hasText(subject, "subject cannot be left blank");
|
||||
|
||||
long tenantId = resolveTenantId();
|
||||
PayOrderModel existing =
|
||||
payOrderRepository.findByTenantIdAndBizOrderNo(tenantId, bizOrderNo).orElse(null);
|
||||
if (existing != null) {
|
||||
if (existing.getPayMode() != null && existing.getPayMode() != payMode) {
|
||||
return recreateForSwitchedPayMode(existing, payMode, orderAmount, subject, ext);
|
||||
}
|
||||
return resumeExistingOrder(existing, payMode);
|
||||
}
|
||||
|
||||
PayOrderModel row = PayOrderModel.builder()
|
||||
.tenantId(tenantId)
|
||||
.bizOrderNo(bizOrderNo)
|
||||
.bizScene(null)
|
||||
.orderAmount(orderAmount)
|
||||
.subject(subject)
|
||||
.ext(ext)
|
||||
.payMode(payMode)
|
||||
.platformFee(0L)
|
||||
.providerFee(0L)
|
||||
.netAmount(0L)
|
||||
.gatewaySyncStatus(PayOrderGatewaySyncStatus.PENDING)
|
||||
.build();
|
||||
try {
|
||||
row = payOrderRepository.save(row);
|
||||
} catch (DuplicateKeyException e) {
|
||||
PayOrderModel race = payOrderRepository.findByTenantIdAndBizOrderNo(tenantId, bizOrderNo).orElse(null);
|
||||
if (race == null) {
|
||||
throw new BizException(ErrorCodeEnum.SYS_ERROR.getCode(), "创建订单冲突,请重试");
|
||||
}
|
||||
assertPayMode(race, payMode);
|
||||
return resumeExistingOrder(race, payMode);
|
||||
}
|
||||
return callGatewayAndFinish(row, payMode);
|
||||
}
|
||||
|
||||
private static void assertPayMode(PayOrderModel row, PayMode expectedPayMode) {
|
||||
if (row.getPayMode() == null || row.getPayMode() != expectedPayMode) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "订单支付方式不匹配");
|
||||
}
|
||||
}
|
||||
|
||||
private OrderCreateResponse recreateForSwitchedPayMode(
|
||||
PayOrderModel row, PayMode targetPayMode, long orderAmount, String subject, String ext) {
|
||||
row.setPayMode(targetPayMode);
|
||||
row.setOrderAmount(orderAmount);
|
||||
row.setSubject(subject);
|
||||
row.setExt(ext);
|
||||
row.setPayChannel(null);
|
||||
row.setPlatformFee(0L);
|
||||
row.setProviderFee(0L);
|
||||
row.setNetAmount(0L);
|
||||
row.setGatewayPaymentOrderNo(null);
|
||||
row.setGatewayOrderStatus(null);
|
||||
row.setGatewayLastError(null);
|
||||
row.setPaidAt(null);
|
||||
row.setGatewaySyncStatus(PayOrderGatewaySyncStatus.PENDING);
|
||||
row.setBizNotifyStatus(null);
|
||||
payOrderRepository.save(row);
|
||||
return callGatewayAndFinish(row, targetPayMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 已存在行的续跑逻辑。FAILED→PENDING 使用 CAS(仅一行匹配 FAILED 才更新),降低多实例同时重试时对网关的重复调用。
|
||||
* 网关已 SUCCESS 时幂等返回已有单号(minipay 可能尚未调起渠道)。
|
||||
*/
|
||||
private PaymentOrderCreateResponse resumeExistingOrder(PayOrderModel row) {
|
||||
private OrderCreateResponse resumeExistingOrder(PayOrderModel row, PayMode payMode) {
|
||||
if (PayOrderGatewaySyncStatus.SUCCESS == row.getGatewaySyncStatus()) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "订单已存在");
|
||||
return toOrderCreateResponse(row);
|
||||
}
|
||||
if (PayOrderGatewaySyncStatus.PENDING == row.getGatewaySyncStatus()) {
|
||||
if (!isStalePending(row)) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "订单处理中,请稍后再试");
|
||||
}
|
||||
// 长时间pending,可能是因为即将调用网关时发生了重启,没有调下游,允许重新调用,依赖下游幂等
|
||||
return callGatewayAndFinish(row);
|
||||
return callGatewayAndFinish(row, payMode);
|
||||
}
|
||||
if (PayOrderGatewaySyncStatus.FAILED == row.getGatewaySyncStatus()) {
|
||||
return casRetryFailedToPendingThenCallGateway(row);
|
||||
return casRetryFailedToPendingThenCallGateway(row, payMode);
|
||||
}
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "订单状态异常,无法创建");
|
||||
}
|
||||
|
||||
private PaymentOrderCreateResponse casRetryFailedToPendingThenCallGateway(PayOrderModel row) {
|
||||
private OrderCreateResponse casRetryFailedToPendingThenCallGateway(PayOrderModel row, PayMode payMode) {
|
||||
int n = payOrderRepository.casUpdateGatewaySyncFromFailedToPending(row.getId(), row.getTenantId(), row.getBizOrderNo());
|
||||
if (n == 1) {
|
||||
row.setGatewaySyncStatus(PayOrderGatewaySyncStatus.PENDING);
|
||||
row.setGatewayLastError(null);
|
||||
return callGatewayAndFinish(row);
|
||||
return callGatewayAndFinish(row, payMode);
|
||||
}
|
||||
PayOrderModel fresh = payOrderRepository
|
||||
.findByTenantIdAndBizOrderNo(row.getTenantId(), row.getBizOrderNo())
|
||||
@@ -178,7 +410,25 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
if (fresh == null) {
|
||||
throw new BizException(ErrorCodeEnum.SYS_ERROR.getCode(), "订单冲突,请重试");
|
||||
}
|
||||
return resumeExistingOrder(fresh);
|
||||
assertPayMode(fresh, payMode);
|
||||
return resumeExistingOrder(fresh, payMode);
|
||||
}
|
||||
|
||||
private static OrderCreateResponse toOrderCreateResponse(PayOrderModel row) {
|
||||
PaymentStatus status = null;
|
||||
if (StringUtils.hasText(row.getGatewayOrderStatus())) {
|
||||
try {
|
||||
status = PaymentStatus.valueOf(row.getGatewayOrderStatus());
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// ignore unmapped gateway status string
|
||||
}
|
||||
}
|
||||
return OrderCreateResponse.builder()
|
||||
.gatewayPaymentOrderNo(row.getGatewayPaymentOrderNo())
|
||||
.bizOrderNo(row.getBizOrderNo())
|
||||
.status(status)
|
||||
.payMode(row.getPayMode())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static boolean isStalePending(PayOrderModel row) {
|
||||
@@ -189,22 +439,48 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
return anchor.toInstant().plus(PayOrderScheduleConstants.STALE_GATEWAY_SYNC_PENDING).isBefore(Instant.now());
|
||||
}
|
||||
|
||||
private PaymentOrderCreateResponse callGatewayAndFinish(PayOrderModel row) {
|
||||
private OrderCreateResponse callGatewayAndFinish(PayOrderModel row, PayMode payMode) {
|
||||
try {
|
||||
PaymentOrderCreateResponse resp =
|
||||
paymentScanGateway.createOrder(
|
||||
payGatewayOutboundExecutor.resolve(row.getTenantId()),
|
||||
row.getBizOrderNo(),
|
||||
row.getOrderAmount(),
|
||||
row.getSubject(),
|
||||
row.getExt());
|
||||
OrderCreateResponse resp;
|
||||
if (payMode == PayMode.minipay) {
|
||||
resp = paymentMiniPayGateway.createOrder(
|
||||
payGatewayOutboundExecutor.resolve(row.getTenantId()),
|
||||
row.getBizOrderNo(),
|
||||
row.getOrderAmount(),
|
||||
row.getSubject(),
|
||||
row.getExt());
|
||||
} else if (payMode == PayMode.h5) {
|
||||
resp = paymentH5Gateway.createOrder(
|
||||
payGatewayOutboundExecutor.resolve(row.getTenantId()),
|
||||
row.getBizOrderNo(),
|
||||
row.getOrderAmount(),
|
||||
row.getSubject(),
|
||||
row.getExt());
|
||||
} else if (payMode == PayMode.app) {
|
||||
resp = paymentAppGateway.createOrder(
|
||||
payGatewayOutboundExecutor.resolve(row.getTenantId()),
|
||||
row.getBizOrderNo(),
|
||||
row.getOrderAmount(),
|
||||
row.getSubject(),
|
||||
row.getExt());
|
||||
} else {
|
||||
resp = paymentScanGateway.createOrder(
|
||||
payGatewayOutboundExecutor.resolve(row.getTenantId()),
|
||||
row.getBizOrderNo(),
|
||||
row.getOrderAmount(),
|
||||
row.getSubject(),
|
||||
row.getExt());
|
||||
}
|
||||
row.setGatewayPaymentOrderNo(resp.getGatewayPaymentOrderNo());
|
||||
row.setGatewayOrderStatus(resp.getStatus() != null ? resp.getStatus().name() : null);
|
||||
row.setGatewaySyncStatus(PayOrderGatewaySyncStatus.SUCCESS);
|
||||
row.setGatewayLastError(null);
|
||||
row.setBizNotifyStatus(PayBizNotifyStatus.POLLING);
|
||||
payOrderRepository.save(row);
|
||||
payOrderPayPollRunner.startPoll(row.getId(), row.getTenantId());
|
||||
if (payMode == PayMode.scan) {
|
||||
row.setBizNotifyStatus(PayBizNotifyStatus.POLLING);
|
||||
payOrderRepository.save(row);
|
||||
payOrderPayPollRunner.startPoll(row.getId(), row.getTenantId());
|
||||
}
|
||||
return resp;
|
||||
} catch (PayGatewayClientException e) {
|
||||
PayOrderGatewaySyncFailureSupport.finishGatewayCreateFailed(
|
||||
@@ -213,6 +489,43 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateMiniPayChannelParams(MiniPayTransactionRpcCreateRequest request) {
|
||||
if (request.getPayChannel() == PayChannel.WxPay) {
|
||||
Assert.hasText(request.getSubAppid(), "subAppid cannot be left blank for WxPay");
|
||||
Assert.hasText(request.getOpenId(), "openId cannot be left blank for WxPay");
|
||||
return;
|
||||
}
|
||||
if (request.getPayChannel() == PayChannel.AliPay) {
|
||||
Assert.hasText(request.getBuyerId(), "buyerId cannot be left blank for AliPay");
|
||||
return;
|
||||
}
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "小程序支付仅支持 WxPay 与 AliPay");
|
||||
}
|
||||
|
||||
private static void validateAppPayChannel(PayChannel payChannel) {
|
||||
if (payChannel != PayChannel.WxPay && payChannel != PayChannel.AliPay) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "App 原生支付仅支持 WxPay 与 AliPay");
|
||||
}
|
||||
}
|
||||
|
||||
private static void mergeTransactionResponseIntoRow(PayOrderModel row, OrderAndTransactionCreateResponse resp) {
|
||||
if (resp.getPlatformFee() != null) {
|
||||
row.setPlatformFee(resp.getPlatformFee());
|
||||
}
|
||||
if (resp.getProviderFee() != null) {
|
||||
row.setProviderFee(resp.getProviderFee());
|
||||
}
|
||||
if (resp.getNetAmount() != null) {
|
||||
row.setNetAmount(resp.getNetAmount());
|
||||
}
|
||||
if (resp.getPayChannel() != null) {
|
||||
row.setPayChannel(resp.getPayChannel());
|
||||
}
|
||||
if (resp.getStatus() != null) {
|
||||
row.setGatewayOrderStatus(resp.getStatus().name());
|
||||
}
|
||||
}
|
||||
|
||||
private static long resolveTenantId() {
|
||||
Long tenantId = RequestContext.get() != null ? RequestContext.get().getTenantId() : null;
|
||||
if (tenantId == null) {
|
||||
@@ -227,4 +540,21 @@ public class PaymentRpcServiceImpl implements IPaymentRpcService {
|
||||
}
|
||||
return s.length() > max ? s.substring(0, max) : s;
|
||||
}
|
||||
|
||||
private static String validateFrontNotifyUrl(String url) {
|
||||
try {
|
||||
URI uri = URI.create(url);
|
||||
String scheme = uri.getScheme();
|
||||
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "frontNotifyUrl must start with http or https");
|
||||
}
|
||||
String host = uri.getHost();
|
||||
if (host == null || host.isBlank()) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "Invalid frontNotifyUrl");
|
||||
}
|
||||
return url;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "Invalid frontNotifyUrl");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
package com.xspaceagi.pay.application.support;
|
||||
|
||||
import com.xspaceagi.bill.sdk.rpc.IBillRpcService;
|
||||
import com.xspaceagi.bill.sdk.dto.AddRevenueRequest;
|
||||
import com.xspaceagi.bill.spec.enums.PayStatusEnum;
|
||||
import com.xspaceagi.bill.spec.enums.RevenueTargetTypeEnum;
|
||||
import com.xspaceagi.bill.spec.enums.RevenueTypeEnum;
|
||||
import com.xspaceagi.pay.domain.model.PayOrderModel;
|
||||
import com.xspaceagi.pay.sdk.dto.ScanOrderStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.enums.PaymentStatus;
|
||||
import com.xspaceagi.pay.sdk.support.PayOrderExtKeys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** 支付结果通知 Bill(进程内 RPC)。 */
|
||||
@Slf4j
|
||||
public final class PayOrderBillNotifySupport {
|
||||
@@ -44,9 +53,13 @@ public final class PayOrderBillNotifySupport {
|
||||
* @return true 表示业务通知已成功(或无 Bill 回调目标)
|
||||
*/
|
||||
public static boolean notifyBillPaymentCallback(
|
||||
IBillRpcService billRpcService, PayOrderModel row, ScanOrderStatusQueryResponse gatewayResp, boolean pollTimeout) {
|
||||
IBillRpcService billRpcService, PayOrderModel row, PaymentStatusQueryResponse gatewayResp, boolean pollTimeout) {
|
||||
Long orderId = parseNumericBizOrderNo(row.getBizOrderNo());
|
||||
if (orderId == null) {
|
||||
// 通用项目订单(GP1_/GP2_)— 无 Bill 回调目标;若已支付则计入开发者收益
|
||||
if (gatewayResp != null && gatewayResp.getStatus() == PaymentStatus.PAID) {
|
||||
return addGeneralProjectRevenue(billRpcService, row);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
PaymentStatus st = gatewayResp != null ? gatewayResp.getStatus() : null;
|
||||
@@ -66,6 +79,87 @@ public final class PayOrderBillNotifySupport {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用项目支付成功 → 计入开发者(项目创建者)收益。
|
||||
* 从 ext 解析 generalProjectUserId 和 generalProjectId。
|
||||
* userId 为 null 时跳过(不阻断通知流程);addRevenue 幂等,异常返回 false 触发重试。
|
||||
*
|
||||
* @return true 表示收益已记录或无需记录;false 表示记录失败应重试
|
||||
*/
|
||||
private static boolean addGeneralProjectRevenue(IBillRpcService billRpcService, PayOrderModel row) {
|
||||
Map<String, Object> extMap = PayOrderExtSupport.parseExt(row.getExt());
|
||||
Long userId = parseLongExt(extMap.get(PayOrderExtKeys.GENERAL_PROJECT_USER_ID));
|
||||
Long projectId = parseLongExt(extMap.get(PayOrderExtKeys.GENERAL_PROJECT_ID));
|
||||
if (userId == null) {
|
||||
log.warn(
|
||||
"[pay-notify] general-project revenue skipped (no userId) payOrderId={} tenantId={}",
|
||||
row.getId(),
|
||||
row.getTenantId());
|
||||
return true;
|
||||
}
|
||||
AddRevenueRequest req = new AddRevenueRequest();
|
||||
req.setTenantId(row.getTenantId());
|
||||
req.setUserId(userId);
|
||||
req.setBizNo(row.getBizOrderNo());
|
||||
req.setType(RevenueTypeEnum.PROJECT_PAY);
|
||||
req.setTargetType(RevenueTargetTypeEnum.PROJECT);
|
||||
if (projectId != null) {
|
||||
req.setTargetId(projectId);
|
||||
}
|
||||
req.setRemark(row.getSubject());
|
||||
Map<String, Object> extra = new HashMap<>();
|
||||
extra.put("payOrderId", row.getId());
|
||||
if (row.getBizOrderNo() != null) {
|
||||
extra.put("bizOrderNo", row.getBizOrderNo());
|
||||
}
|
||||
if (row.getGatewayPaymentOrderNo() != null) {
|
||||
extra.put("gatewayPaymentOrderNo", row.getGatewayPaymentOrderNo());
|
||||
}
|
||||
req.setExtra(extra);
|
||||
try {
|
||||
if (row.getOrderAmount() == null) {
|
||||
log.warn(
|
||||
"[pay-notify] general-project revenue skipped (null orderAmount) payOrderId={} tenantId={}",
|
||||
row.getId(),
|
||||
row.getTenantId());
|
||||
return true;
|
||||
}
|
||||
req.setAmount(new BigDecimal(row.getOrderAmount()).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP));
|
||||
billRpcService.addRevenue(req);
|
||||
log.info(
|
||||
"[pay-notify] general-project revenue added payOrderId={} tenantId={} userId={} projectId={} amount={}",
|
||||
row.getId(),
|
||||
row.getTenantId(),
|
||||
userId,
|
||||
projectId,
|
||||
req.getAmount());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.warn(
|
||||
"[pay-notify] general-project revenue failed payOrderId={} tenantId={} userId={} msg={}",
|
||||
row.getId(),
|
||||
row.getTenantId(),
|
||||
userId,
|
||||
e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 安全解析 ext 中的数字字段(Integer / Long / String → Long)。 */
|
||||
private static Long parseLongExt(Object val) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
if (val instanceof Number) {
|
||||
return ((Number) val).longValue();
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(val.toString().trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Long parseNumericBizOrderNo(String bizOrderNo) {
|
||||
if (bizOrderNo == null || bizOrderNo.isBlank()) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.xspaceagi.pay.application.support;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.xspaceagi.pay.sdk.support.PayOrderExtKeys;
|
||||
import com.xspaceagi.pay.sdk.enums.PayClientScene;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class PayOrderExtSupport {
|
||||
|
||||
private PayOrderExtSupport() {}
|
||||
|
||||
public static String mergePayClientScene(String extJson, PayClientScene scene) {
|
||||
if (scene == null) {
|
||||
return extJson;
|
||||
}
|
||||
Map<String, Object> map = parseExt(extJson);
|
||||
map.put(PayOrderExtKeys.PAY_CLIENT_SCENE, scene.name());
|
||||
return JSON.toJSONString(map);
|
||||
}
|
||||
|
||||
public static Map<String, Object> parseExt(String extJson) {
|
||||
if (extJson == null || extJson.isBlank()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
Map<String, Object> parsed = JSON.parseObject(extJson);
|
||||
return parsed != null ? parsed : new HashMap<>();
|
||||
} catch (Exception e) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.xspaceagi.pay.application.support;
|
||||
|
||||
import com.xspaceagi.pay.domain.model.PayOrderModel;
|
||||
import com.xspaceagi.pay.sdk.dto.ScanOrderStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryResponse;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@@ -20,7 +20,7 @@ public final class PayOrderGatewayQueryMerger {
|
||||
|
||||
private PayOrderGatewayQueryMerger() {}
|
||||
|
||||
public static void mergeIntoRow(PayOrderModel row, ScanOrderStatusQueryResponse r) {
|
||||
public static void mergeIntoRow(PayOrderModel row, PaymentStatusQueryResponse r) {
|
||||
if (r == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.xspaceagi.pay.application.support;
|
||||
|
||||
import com.xspaceagi.bill.sdk.rpc.IBillRpcService;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentScanGateway;
|
||||
import com.xspaceagi.pay.domain.model.PayOrderModel;
|
||||
import com.xspaceagi.pay.domain.repository.PayOrderRepository;
|
||||
import com.xspaceagi.pay.sdk.dto.ScanOrderStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.enums.PaymentStatus;
|
||||
import com.xspaceagi.pay.spec.enums.PayBizNotifyStatus;
|
||||
import com.xspaceagi.pay.spec.enums.PayOrderGatewaySyncStatus;
|
||||
@@ -25,7 +24,7 @@ import org.springframework.util.StringUtils;
|
||||
public class PayOrderSettlementSyncService {
|
||||
|
||||
private final PayOrderRepository payOrderRepository;
|
||||
private final PaymentScanGateway paymentScanGateway;
|
||||
private final PayOrderStatusQuerySupport payOrderStatusQuerySupport;
|
||||
private final PayGatewayOutboundExecutor payGatewayOutboundExecutor;
|
||||
private final IBillRpcService billRpcService;
|
||||
|
||||
@@ -50,10 +49,10 @@ public class PayOrderSettlementSyncService {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScanOrderStatusQueryResponse last;
|
||||
PaymentStatusQueryResponse last;
|
||||
try {
|
||||
PayGatewayOutbound outbound = payGatewayOutboundExecutor.resolveCached(tenantId);
|
||||
last = paymentScanGateway.queryOrderStatus(outbound, row.getGatewayPaymentOrderNo(), true);
|
||||
last = payOrderStatusQuerySupport.queryOrderStatus(outbound, row, true);
|
||||
PayOrderGatewayQueryMerger.mergeIntoRow(row, last);
|
||||
payOrderRepository.save(row);
|
||||
} catch (PayGatewayClientException e) {
|
||||
@@ -78,7 +77,7 @@ public class PayOrderSettlementSyncService {
|
||||
}
|
||||
|
||||
private boolean notifyBillAndMark(
|
||||
PayOrderModel row, ScanOrderStatusQueryResponse last, PayBizNotifyStatus expectedCurrent) {
|
||||
PayOrderModel row, PaymentStatusQueryResponse last, PayBizNotifyStatus expectedCurrent) {
|
||||
if (!PayOrderBillNotifySupport.notifyBillPaymentCallback(billRpcService, row, last, false)) {
|
||||
log.warn("[pay-settlement-sync] bill notify failed bizOrderNo={}", row.getBizOrderNo());
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.xspaceagi.pay.application.support;
|
||||
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentAppGateway;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentH5Gateway;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentMiniPayGateway;
|
||||
import com.xspaceagi.pay.domain.gateway.PaymentScanGateway;
|
||||
import com.xspaceagi.pay.domain.model.PayOrderModel;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryResponse;
|
||||
import com.xspaceagi.pay.sdk.enums.PayMode;
|
||||
import com.xspaceagi.pay.spec.gateway.PayGatewayOutbound;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 查询支付状态。 */
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PayOrderStatusQuerySupport {
|
||||
|
||||
private final PaymentScanGateway paymentScanGateway;
|
||||
private final PaymentMiniPayGateway paymentMiniPayGateway;
|
||||
private final PaymentH5Gateway paymentH5Gateway;
|
||||
private final PaymentAppGateway paymentAppGateway;
|
||||
|
||||
public PaymentStatusQueryResponse queryOrderStatus(
|
||||
PayGatewayOutbound outbound, PayOrderModel row, boolean syncFromChannel) {
|
||||
if (row.getPayMode() == PayMode.minipay) {
|
||||
return paymentMiniPayGateway.queryOrderStatus(outbound, row.getGatewayPaymentOrderNo(), syncFromChannel);
|
||||
}
|
||||
if (row.getPayMode() == PayMode.h5) {
|
||||
return paymentH5Gateway.queryOrderStatus(outbound, row.getGatewayPaymentOrderNo(), syncFromChannel);
|
||||
}
|
||||
if (row.getPayMode() == PayMode.app) {
|
||||
return paymentAppGateway.queryOrderStatus(outbound, row.getGatewayPaymentOrderNo(), syncFromChannel);
|
||||
}
|
||||
return paymentScanGateway.queryOrderStatus(outbound, row.getGatewayPaymentOrderNo(), syncFromChannel);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user