LinePayReconcileTask.java 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package com.ruoyi.app.task;
  2. import com.ruoyi.app.pay.LinePayRefundService;
  3. import com.ruoyi.app.pay.LinePayCancellationCompensationService;
  4. import com.ruoyi.app.pay.LinePayService;
  5. import com.ruoyi.app.pay.LinePayOfflineService;
  6. import com.ruoyi.system.domain.PosOrderLinePayment;
  7. import com.ruoyi.system.domain.PosOrderLineRefund;
  8. import com.ruoyi.system.service.IPosOrderLinePaymentService;
  9. import com.ruoyi.system.service.IPosOrderLineRefundService;
  10. import org.redisson.api.RLock;
  11. import org.redisson.api.RedissonClient;
  12. import org.slf4j.Logger;
  13. import org.slf4j.LoggerFactory;
  14. import org.springframework.scheduling.annotation.Scheduled;
  15. import org.springframework.stereotype.Component;
  16. import org.springframework.beans.factory.annotation.Value;
  17. import java.util.Date;
  18. import java.util.List;
  19. import java.util.UUID;
  20. /** Periodically retrieves LINE payment/refund facts and resumes recoverable state. */
  21. @Component
  22. public class LinePayReconcileTask {
  23. private static final Logger log = LoggerFactory.getLogger(LinePayReconcileTask.class);
  24. private static final String LOCK_KEY = "lock:line-pay:reconcile";
  25. @Value("${line-pay.reconcile.batch-size:20}")
  26. private int batchSize = 20;
  27. @Value("${line-pay.reconcile.row-lease-seconds:120}")
  28. private long rowLeaseSeconds = 120L;
  29. @Value("${line-pay.reconcile.round-budget-seconds:45}")
  30. private long roundBudgetSeconds = 45L;
  31. private final IPosOrderLinePaymentService paymentService;
  32. private final IPosOrderLineRefundService refundService;
  33. private final LinePayService linePayService;
  34. private final LinePayOfflineService linePayOfflineService;
  35. private final LinePayRefundService linePayRefundService;
  36. private final RedissonClient redissonClient;
  37. private final LinePayCancellationCompensationService cancellationCompensationService;
  38. public LinePayReconcileTask(IPosOrderLinePaymentService paymentService,
  39. IPosOrderLineRefundService refundService,
  40. LinePayService linePayService,
  41. LinePayRefundService linePayRefundService,
  42. RedissonClient redissonClient,
  43. LinePayCancellationCompensationService cancellationCompensationService) {
  44. this(paymentService, refundService, linePayService, null, linePayRefundService,
  45. redissonClient, cancellationCompensationService);
  46. }
  47. @org.springframework.beans.factory.annotation.Autowired
  48. public LinePayReconcileTask(IPosOrderLinePaymentService paymentService,
  49. IPosOrderLineRefundService refundService,
  50. LinePayService linePayService,
  51. LinePayOfflineService linePayOfflineService,
  52. LinePayRefundService linePayRefundService,
  53. RedissonClient redissonClient,
  54. LinePayCancellationCompensationService cancellationCompensationService) {
  55. this.paymentService = paymentService;
  56. this.refundService = refundService;
  57. this.linePayService = linePayService;
  58. this.linePayOfflineService = linePayOfflineService;
  59. this.linePayRefundService = linePayRefundService;
  60. this.redissonClient = redissonClient;
  61. this.cancellationCompensationService = cancellationCompensationService;
  62. }
  63. @Scheduled(fixedDelayString = "${line-pay.reconcile.fixed-delay-ms:60000}",
  64. initialDelayString = "${line-pay.reconcile.initial-delay-ms:60000}")
  65. public void reconcile() {
  66. RLock lock = redissonClient.getLock(LOCK_KEY);
  67. if (!lock.tryLock()) {
  68. return;
  69. }
  70. try {
  71. reconcileRound();
  72. } catch (Exception exception) {
  73. log.error("LINE Pay scheduled reconcile failed", exception);
  74. } finally {
  75. if (lock.isHeldByCurrentThread()) {
  76. lock.unlock();
  77. }
  78. }
  79. }
  80. private void reconcileRound() {
  81. long started = System.currentTimeMillis();
  82. Date now = new Date(started);
  83. List<PosOrderLinePayment> payments = paymentService.scanDue(now, batchSize);
  84. for (PosOrderLinePayment payment : payments) {
  85. if (roundBudgetExceeded(started)) {
  86. break;
  87. }
  88. String owner = "TASK-" + UUID.randomUUID();
  89. if (paymentService.claimReconcileLease(payment.getId(), payment.getVersion(), owner,
  90. new Date(System.currentTimeMillis() + rowLeaseSeconds * 1000L), new Date()) != 1) {
  91. continue;
  92. }
  93. try {
  94. if ("OFFLINE".equals(payment.getPaymentMode()) && linePayOfflineService != null) {
  95. linePayOfflineService.reconcilePayment(payment.getId(), "TASK");
  96. } else {
  97. linePayService.reconcilePayment(payment.getId(), "TASK", owner);
  98. }
  99. } catch (Exception exception) {
  100. log.warn("LINE Pay payment reconcile failed, paymentId={}", payment.getId());
  101. }
  102. }
  103. repairCancelledPayments();
  104. List<PosOrderLineRefund> refunds = refundService.scanDue(new Date(), batchSize);
  105. long refundStarted = System.currentTimeMillis();
  106. boolean processedRefund = false;
  107. for (PosOrderLineRefund refund : refunds) {
  108. if (processedRefund && roundBudgetExceeded(refundStarted)) {
  109. break;
  110. }
  111. try {
  112. linePayRefundService.requestFullRefund(refund.getPaymentId(), "TASK");
  113. } catch (Exception exception) {
  114. log.warn("LINE Pay refund reconcile failed, paymentId={}", refund.getPaymentId());
  115. }
  116. processedRefund = true;
  117. }
  118. }
  119. private void repairCancelledPayments() {
  120. cancellationCompensationService.createMissingRefundIntents(
  121. paymentService.getCancelledPaidWithoutRefund(batchSize));
  122. }
  123. private boolean roundBudgetExceeded(long started) {
  124. return System.currentTimeMillis() - started >= roundBudgetSeconds * 1000L;
  125. }
  126. }