流水扫描查询任务
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/DtlQueryDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/DtlQueryDao.java
new file mode 100644
index 0000000..ea6c3be
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/DtlQueryDao.java
@@ -0,0 +1,13 @@
+package com.supwisdom.dlpay.api.dao;
+
+import com.supwisdom.dlpay.api.domain.TDtlQuery;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+
+public interface DtlQueryDao extends JpaRepository<TDtlQuery, String> {
+
+  @Query("from TDtlQuery t where t.accdate=?1 and t.status=?2 and t.qcnt<=?3 order by t.lastsaved desc ")
+  List<TDtlQuery> getNeedQueryDtls(String accdate, String status, int maxQcnt);
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TDtlQuery.java b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TDtlQuery.java
new file mode 100644
index 0000000..feec7c5
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TDtlQuery.java
@@ -0,0 +1,115 @@
+package com.supwisdom.dlpay.api.domain;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+
+@Entity
+@Table(name = "TB_DTL_QUERY")
+public class TDtlQuery {
+  @Id
+  @GenericGenerator(name = "idGenerator", strategy = "uuid")
+  @GeneratedValue(generator = "idGenerator")
+  @Column(name = "id", nullable = false, length = 32)
+  private String id;
+
+  @Column(name = "ACCDATE", nullable = false, length = 32)
+  private String accdate;
+
+  @Column(name = "REFNO", nullable = false, length = 32)
+  private String refno;
+
+  @Column(name = "STATUS", nullable = false, length = 32)
+  private String status;
+
+  @Column(name = "REMARK", length = 600)
+  private String remark;
+
+  @Column(name = "QCNT", length = 9)
+  private Integer qcnt;
+
+  @Column(name = "LASTSAVED")
+  @Version
+  private Timestamp lastsaved;
+
+  @Column(name = "tenantid",  nullable = false, length = 20)
+  private String tenantId;
+
+  public TDtlQuery(){
+  }
+
+  public TDtlQuery(String accdate, String refno, String status, String remark, Integer qcnt, Timestamp lastsaved, String tenantId) {
+    this.accdate = accdate;
+    this.refno = refno;
+    this.status = status;
+    this.remark = remark;
+    this.qcnt = qcnt;
+    this.lastsaved = lastsaved;
+    this.tenantId = tenantId;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getAccdate() {
+    return accdate;
+  }
+
+  public void setAccdate(String accdate) {
+    this.accdate = accdate;
+  }
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  public String getRemark() {
+    return remark;
+  }
+
+  public void setRemark(String remark) {
+    this.remark = remark;
+  }
+
+  public Integer getQcnt() {
+    return qcnt;
+  }
+
+  public void setQcnt(Integer qcnt) {
+    this.qcnt = qcnt;
+  }
+
+  public Timestamp getLastsaved() {
+    return lastsaved;
+  }
+
+  public void setLastsaved(Timestamp lastsaved) {
+    this.lastsaved = lastsaved;
+  }
+
+  public String getTenantId() {
+    return tenantId;
+  }
+
+  public void setTenantId(String tenantId) {
+    this.tenantId = tenantId;
+  }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/ConstantUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/util/ConstantUtil.java
index 6cacabd..3c7b5de 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/util/ConstantUtil.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/util/ConstantUtil.java
@@ -29,4 +29,8 @@
    * */
   public static final String CARDTYPE_CITIZENCARD = "citizencard";
   public static final String CARDTYPE_BANKCARD = "bankcard";
+
+  public static final String QUERYTYPE_NEED_QUERY = "query";
+  public static final String QUERYTYPE_QUERY_FINISH = "finish";
+  public static final int QUERY_MAX_COUNT = 10;
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
index 133427f..4139d9e 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
@@ -4,10 +4,15 @@
 import com.supwisdom.dlpay.agent.DtlStatus
 
 import com.supwisdom.dlpay.agent.citizencard.YnrccUtil
+import com.supwisdom.dlpay.api.domain.TDtlQuery
 import com.supwisdom.dlpay.api.domain.TTransactionMain
 import com.supwisdom.dlpay.api.repositories.ShopaccService
+import com.supwisdom.dlpay.api.service.DtlQueryResultService
 import com.supwisdom.dlpay.api.service.TransactionServiceProxy
+import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.ApplicationUtil
+import com.supwisdom.dlpay.framework.util.TradeDict
+import com.supwisdom.dlpay.util.ConstantUtil
 import mu.KotlinLogging
 import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler
 import org.springframework.beans.factory.annotation.Autowired
@@ -70,12 +75,28 @@
     @Autowired
     private lateinit var applicationContext: ApplicationContext
 
+    @Autowired
+    private lateinit var dtlQueryResultService: DtlQueryResultService
+
+    @Autowired
+    private lateinit var systemUtilService: SystemUtilService
+
     @Async("queryAgentPayResult")
     fun queryResult(transaction: TTransactionMain, qcnt: Int) {
         try {
             if (qcnt >= YnrccUtil.QUERY_MAX_COUNT) {
                 //查询超最大次数
                 logger.error("查询refno=[${transaction.refno}]流水结果超最大查询次数[${YnrccUtil.QUERY_MAX_COUNT}]")
+
+                //保存进查询表,定时扫描任务去查询
+                dtlQueryResultService.saveOrUpdateDtlQuery(TDtlQuery().apply {
+                    this.accdate = transaction.accdate
+                    this.refno = transaction.refno
+                    this.status = ConstantUtil.QUERYTYPE_NEED_QUERY
+                    this.remark = null
+                    this.qcnt = 0
+                    this.lastsaved = systemUtilService.sysdatetime.sysdate
+                })
                 return
             }
 
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_task.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_task.kt
index 0e6d694..38f652f 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_task.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_task.kt
@@ -1,15 +1,30 @@
 package com.supwisdom.dlpay.api
 
+import com.supwisdom.dlpay.agent.AgentCode
+import com.supwisdom.dlpay.agent.DtlStatus
+import com.supwisdom.dlpay.api.domain.TDtlQuery
 import com.supwisdom.dlpay.api.domain.TShopdtl
 import com.supwisdom.dlpay.api.repositories.ShopaccService
+import com.supwisdom.dlpay.api.service.ConsumePayService
+import com.supwisdom.dlpay.api.service.DtlQueryResultService
+import com.supwisdom.dlpay.api.service.TransactionServiceProxy
+import com.supwisdom.dlpay.framework.domain.TTaskLock
+import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.ApplicationUtil
+import com.supwisdom.dlpay.framework.util.TradeDict
+import com.supwisdom.dlpay.util.ConstantUtil
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
 import org.springframework.scheduling.annotation.Scheduled
+import org.springframework.stereotype.Component
 import org.springframework.stereotype.Service
 
 @Service
 class MySchedulerTask {
     @Autowired
     private lateinit var shopaccService: ShopaccService
+    @Autowired
+    private lateinit var systemUtilService: SystemUtilService
 
     fun doShopBlanceUpdate(dtl: TShopdtl) {
         shopaccService.recalcShopBalance(dtl.refno, true)
@@ -17,8 +32,159 @@
 
     @Scheduled(cron = "\${shopbalance.updater.cron:-}")
     fun dealShopUnupdatedDtl() {
-        shopaccService.findUnupdatedShopDtl(100).forEach {
-            doShopBlanceUpdate(it)
+        var tasklock = TTaskLock()
+        try {
+            //集群控制
+            try {
+                tasklock = systemUtilService.doLockTask("SHOPBLANCEUPDATETASK", 10, "更新账户余额任务")
+                if (tasklock == null) {
+                    return
+                }
+            } catch (e: Exception) {
+                return
+            }
+
+            shopaccService.findUnupdatedShopDtl(100).forEach {
+                doShopBlanceUpdate(it)
+            }
+        } catch (ex: Exception) {
+            ex.printStackTrace()
+        } finally {
+            if (tasklock?.taskcode.isNotEmpty()) {
+                tasklock.taskstatus = 0
+                tasklock.tasktime = systemUtilService.sysdatetime.hostdatetime
+                systemUtilService.updateTaskLock(tasklock)
+            }
+        }
+
+
+    }
+}
+
+/**
+ * 定时扫描TDtlQuery,查询第三方流水状态
+ * */
+@Component
+class DtlQueryResultSchedulerTask {
+    @Autowired
+    private lateinit var systemUtilService: SystemUtilService
+    @Autowired
+    private lateinit var dtlQueryResultService: DtlQueryResultService
+    @Autowired
+    lateinit var transactionService: TransactionServiceProxy
+    @Autowired
+    lateinit var consumePayService: ConsumePayService
+    @Autowired
+    private lateinit var applicationContext: ApplicationContext
+
+    @Scheduled(cron = "\${query.third.transdtl.result.cron:-}")
+    fun queryThirdTransdtlResult() {
+        var tasklock = TTaskLock()
+        try {
+            //集群控制
+            try {
+                tasklock = systemUtilService.doLockTask("QUERYTHIRDTRANSDTLRESULTTASK", 10, "查询第三方流水状态任务")
+                if (tasklock == null) {
+                    return
+                }
+            } catch (e: Exception) {
+                return
+            }
+
+            //仅查询当天数据,查询次数在规定次数之下
+            dtlQueryResultService.getNeedQueryRecords(systemUtilService.accdate, ConstantUtil.QUERY_MAX_COUNT).forEach {
+                try {
+                    doQuery(it)
+                } catch (exp: Exception) {
+                    it.qcnt = it.qcnt + 1
+                    dtlQueryResultService.saveOrUpdateDtlQuery(it) //次数加一
+                }
+            }
+        } catch (ex: Exception) {
+            ex.printStackTrace()
+        } finally {
+            if (tasklock?.taskcode.isNotEmpty()) {
+                tasklock.taskstatus = 0
+                tasklock.tasktime = systemUtilService.sysdatetime.hostdatetime
+                systemUtilService.updateTaskLock(tasklock)
+            }
         }
     }
+
+    private fun doQuery(dtlQuery: TDtlQuery) {
+        if (ConstantUtil.QUERYTYPE_NEED_QUERY != dtlQuery.status) {
+            return //已结束
+        }
+        val dtl = consumePayService.getTransactionMainDtl(dtlQuery.refno, null, null)
+        if (null == dtl) {
+            dtlQuery.qcnt = dtlQuery.qcnt + 1
+            dtlQuery.status = ConstantUtil.QUERYTYPE_QUERY_FINISH
+            dtlQuery.remark = "refno未找到流水"
+            dtlQueryResultService.saveOrUpdateDtlQuery(dtlQuery)
+            return
+        } else if (TradeDict.DTL_STATUS_WIP != dtl.status) {
+            dtlQuery.qcnt = dtlQuery.qcnt + 1
+            dtlQuery.status = ConstantUtil.QUERYTYPE_QUERY_FINISH
+            dtlQuery.remark = "原始流水非wip状态"
+            dtlQueryResultService.saveOrUpdateDtlQuery(dtlQuery)
+            return
+        } else if (TradeDict.PAYTYPE_CITIZEN_CARD != dtl.sourceType) {
+            //fixme: 现仅供大理农商卡对接查询
+            dtlQuery.qcnt = dtlQuery.qcnt + 1
+            dtlQuery.status = ConstantUtil.QUERYTYPE_QUERY_FINISH
+            dtlQuery.remark = "非大理市民卡支付流水"
+            dtlQueryResultService.saveOrUpdateDtlQuery(dtlQuery)
+            return
+        }
+
+        val service = ApplicationUtil.findAgentPayService(applicationContext, dtl.sourceType + "Agent")
+        val resp = service.query(dtl)
+        when (resp.code) {
+            AgentCode.SUCCESS -> {
+                //查询成功
+                when (resp.dtlStatus) {
+                    DtlStatus.SUCCESS -> {
+                        transactionService.success(dtl.refno, resp.agentRefno, false) //流水成功
+                        dtlQuery.qcnt = dtlQuery.qcnt + 1
+                        dtlQuery.status = ConstantUtil.QUERYTYPE_QUERY_FINISH
+                        dtlQuery.remark = "查询成功,流水为付款成功状态"
+                        dtlQueryResultService.saveOrUpdateDtlQuery(dtlQuery)
+                        return
+                    }
+                    DtlStatus.REFUND, DtlStatus.PARTIAL_REFUND -> {
+                        dtlQuery.qcnt = dtlQuery.qcnt + 1
+                        dtlQuery.status = ConstantUtil.QUERYTYPE_QUERY_FINISH
+                        dtlQuery.remark = "查询成功,流水为已退款或部分退款状态"
+                        dtlQueryResultService.saveOrUpdateDtlQuery(dtlQuery)
+                        return
+                    }
+                    else -> {
+                        //流水失败
+                        transactionService.fail(dtl.refno, "查询流水状态为交易失败")
+                        dtlQuery.qcnt = dtlQuery.qcnt + 1
+                        dtlQuery.status = ConstantUtil.QUERYTYPE_QUERY_FINISH
+                        dtlQuery.remark = "查询成功,流水为失败状态"
+                        dtlQueryResultService.saveOrUpdateDtlQuery(dtlQuery)
+                        return
+                    }
+                }
+            }
+            AgentCode.REFNO_NOT_EXISTS -> {
+                transactionService.fail(dtl.refno, "银行流水不存在") //银行返回流水不存在
+                dtlQuery.qcnt = dtlQuery.qcnt + 1
+                dtlQuery.status = ConstantUtil.QUERYTYPE_QUERY_FINISH
+                dtlQuery.remark = "查询成功,返回流水不存在"
+                dtlQueryResultService.saveOrUpdateDtlQuery(dtlQuery)
+                return
+            }
+            else -> {
+                //其他明确错误,后续再查询
+                dtlQuery.qcnt = dtlQuery.qcnt + 1
+                dtlQuery.remark = "查询失败!${resp.agentMsg}"
+                dtlQueryResultService.saveOrUpdateDtlQuery(dtlQuery)
+                return
+            }
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/dtl_query_result_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/dtl_query_result_service.kt
new file mode 100644
index 0000000..9673b0f
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/dtl_query_result_service.kt
@@ -0,0 +1,15 @@
+package com.supwisdom.dlpay.api.service
+
+import com.supwisdom.dlpay.api.domain.TDtlQuery
+import org.springframework.transaction.annotation.Propagation
+import org.springframework.transaction.annotation.Transactional
+
+interface DtlQueryResultService {
+
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
+    fun saveOrUpdateDtlQuery(dtlQuery: TDtlQuery): TDtlQuery
+
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class], readOnly = true)
+    fun getNeedQueryRecords(accdate: String, maxCount:Int): List<TDtlQuery>
+
+}
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/dtl_query_result_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/dtl_query_result_service_impl.kt
new file mode 100644
index 0000000..a42427b
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/dtl_query_result_service_impl.kt
@@ -0,0 +1,27 @@
+package com.supwisdom.dlpay.api.service.impl
+
+import com.supwisdom.dlpay.api.dao.DtlQueryDao
+import com.supwisdom.dlpay.api.domain.TDtlQuery
+import com.supwisdom.dlpay.api.service.DtlQueryResultService
+import com.supwisdom.dlpay.framework.tenant.TenantContext
+import com.supwisdom.dlpay.util.ConstantUtil
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+
+@Service
+class DtlQueryResultServiceImpl : DtlQueryResultService {
+    @Autowired
+    private lateinit var dtlQueryDao: DtlQueryDao
+
+    override fun saveOrUpdateDtlQuery(dtlQuery: TDtlQuery): TDtlQuery {
+        if (null == dtlQuery.tenantId) {
+            dtlQuery.tenantId = TenantContext.getTenantSchema()
+        }
+        return dtlQueryDao.save(dtlQuery)
+    }
+
+    override fun getNeedQueryRecords(accdate: String, maxCount: Int): List<TDtlQuery> {
+        return dtlQueryDao.getNeedQueryDtls(accdate, ConstantUtil.QUERYTYPE_NEED_QUERY, maxCount)
+                ?: ArrayList(0)
+    }
+}
\ No newline at end of file
diff --git a/payapi/src/main/resources/application.properties b/payapi/src/main/resources/application.properties
index 17217f9..5ca4e24 100644
--- a/payapi/src/main/resources/application.properties
+++ b/payapi/src/main/resources/application.properties
@@ -30,8 +30,9 @@
 server.tomcat.uri-encoding=UTF-8
 ##################################################
 ## quartz task scheduler
-shopbalance.updater.cron=10/* * * * *
+shopbalance.updater.cron=10/* * * * * ?
 dayend.settletask.cron=0 3/30 2-3 * * ?
+query.third.transdtl.result.cron=7 0/3 * * * ?
 #dayend.settletask.cron = 0 0/2 * * * ?
 ################################################
 # user password