|
|
@@ -0,0 +1,99 @@
|
|
|
+package com.ruoyi.app.omgpay;
|
|
|
+
|
|
|
+import com.ruoyi.app.omgpay.dto.OmgQueryPaymentResponse;
|
|
|
+import com.ruoyi.system.omgpay.domain.OmgPaymentAttempt;
|
|
|
+import com.ruoyi.system.omgpay.service.IOmgPaymentAttemptService;
|
|
|
+import org.redisson.api.RLock;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/** Periodically queries due CREATED attempts to recover payment callbacks that were lost. */
|
|
|
+@Component
|
|
|
+public class OmgPaymentAutoCompensationTask {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(OmgPaymentAutoCompensationTask.class);
|
|
|
+ private static final String LOCK_KEY = "lock:omgpay:automatic-compensation";
|
|
|
+
|
|
|
+ @Value("${omgpay.reconcile.batch-size:20}")
|
|
|
+ int batchSize = 20;
|
|
|
+ @Value("${omgpay.reconcile.retry-delay-ms:300000}")
|
|
|
+ long retryDelayMs = 300000L;
|
|
|
+ @Value("${omgpay.reconcile.round-budget-seconds:45}")
|
|
|
+ long roundBudgetSeconds = 45L;
|
|
|
+
|
|
|
+ private final IOmgPaymentAttemptService attempts;
|
|
|
+ private final OmgPaymentQueryService queryService;
|
|
|
+ private final RedissonClient redissonClient;
|
|
|
+
|
|
|
+ public OmgPaymentAutoCompensationTask(IOmgPaymentAttemptService attempts,
|
|
|
+ OmgPaymentQueryService queryService,
|
|
|
+ RedissonClient redissonClient) {
|
|
|
+ this.attempts = attempts;
|
|
|
+ this.queryService = queryService;
|
|
|
+ this.redissonClient = redissonClient;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Scheduled(fixedDelayString = "${omgpay.reconcile.fixed-delay-ms:60000}",
|
|
|
+ initialDelayString = "${omgpay.reconcile.initial-delay-ms:60000}")
|
|
|
+ public void reconcile() {
|
|
|
+ RLock lock = null;
|
|
|
+ try {
|
|
|
+ lock = redissonClient.getLock(LOCK_KEY);
|
|
|
+ if (!lock.tryLock()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ reconcileRound();
|
|
|
+ } catch (Exception exception) {
|
|
|
+ log.error("OMG automatic compensation round failed", exception);
|
|
|
+ } finally {
|
|
|
+ if (lock != null && lock.isHeldByCurrentThread()) {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void reconcileRound() {
|
|
|
+ long started = System.currentTimeMillis();
|
|
|
+ Date now = new Date(started);
|
|
|
+ List<OmgPaymentAttempt> dueAttempts = attempts.scanDueCreated(now, batchSize);
|
|
|
+ int reserved = 0;
|
|
|
+ int completed = 0;
|
|
|
+ for (OmgPaymentAttempt attempt : dueAttempts) {
|
|
|
+ if (roundBudgetExceeded(started)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ Date nextQueryTime = new Date(System.currentTimeMillis() + retryDelayMs);
|
|
|
+ if (attempts.reserveDueQuery(attempt.getId(), new Date(), nextQueryTime) != 1) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ reserved++;
|
|
|
+ try {
|
|
|
+ OmgQueryPaymentResponse response = queryService.reconcile(attempt);
|
|
|
+ completed++;
|
|
|
+ log.info("OMG automatic compensation processed attemptId={}, orderId={}, merchantTradeNo={}, "
|
|
|
+ + "gatewayTradeStatus={}, localStatus={}",
|
|
|
+ attempt.getId(), OmgPaymentController.safeLogOrderId(attempt.getDdId()),
|
|
|
+ OmgPaymentCreateService.maskMerchantTradeNo(attempt.getMerchantTradeNo()),
|
|
|
+ response.tradeStatus(), response.status());
|
|
|
+ } catch (Exception exception) {
|
|
|
+ log.error("OMG automatic compensation query failed attemptId={}, orderId={}, merchantTradeNo={}",
|
|
|
+ attempt.getId(), OmgPaymentController.safeLogOrderId(attempt.getDdId()),
|
|
|
+ OmgPaymentCreateService.maskMerchantTradeNo(attempt.getMerchantTradeNo()), exception);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!dueAttempts.isEmpty()) {
|
|
|
+ log.info("OMG automatic compensation round completed scanned={}, reserved={}, completed={}",
|
|
|
+ dueAttempts.size(), reserved, completed);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean roundBudgetExceeded(long started) {
|
|
|
+ return System.currentTimeMillis() - started >= roundBudgetSeconds * 1000L;
|
|
|
+ }
|
|
|
+}
|