Merge tag '1.0.29' into develop

脱机码
diff --git a/payapi/build.gradle b/payapi/build.gradle
index 364856c..62de5b2 100644
--- a/payapi/build.gradle
+++ b/payapi/build.gradle
@@ -57,55 +57,89 @@
 
 docker.dependsOn(jar)
 
+repositories {
+    maven {
+        url "http://ykt-nx.supwisdom.com/repository/ecard-repo/"
+        credentials {
+            username 'ecard'
+            password 'Ecard4SUP'
+        }
+    }
+}
+
 dependencies {
-    implementation project(":payapi-common")
+    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+    implementation 'org.springframework.boot:spring-boot-starter-security'
+    implementation 'org.springframework.boot:spring-boot-starter-cache'
+    implementation 'org.springframework.boot:spring-boot-autoconfigure'
+    implementation 'org.springframework.security:spring-security-oauth2-jose'
+    implementation 'org.springframework.security:spring-security-oauth2-client'
+    implementation 'org.springframework.security:spring-security-oauth2-jose'
+    implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.1.5.RELEASE'
+    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
+    implementation 'org.springframework.session:spring-session-data-redis'
+    implementation 'org.springframework.boot:spring-boot-starter-cache'
+    implementation 'org.springframework.kafka:spring-kafka'
+    implementation 'org.springframework.social:spring-social-web:1.1.6.RELEASE'
+    implementation 'org.springframework.kafka:spring-kafka:2.2.8.RELEASE'
+    implementation 'org.jetbrains.kotlin:kotlin-reflect'
+    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
+    implementation 'commons-codec:commons-codec:1.12'
+    implementation 'org.apache.commons:commons-lang3:3.9'
+    implementation 'net.javacrumbs.shedlock:shedlock-spring:2.5.0'
+    implementation 'net.javacrumbs.shedlock:shedlock-provider-redis-spring:2.5.0'
 
-    implementation "org.springframework.boot:spring-boot-starter-web"
-    implementation "org.springframework.boot:spring-boot-starter-security"
-    implementation "org.springframework.boot:spring-boot-starter-cache"
-    implementation "org.springframework.boot:spring-boot-autoconfigure"
-    implementation "org.springframework.security:spring-security-oauth2-jose"
-    implementation "org.springframework.security:spring-security-oauth2-client"
-    implementation "org.springframework.security:spring-security-oauth2-jose"
-    implementation "org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure"
-    implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
-    implementation "org.springframework.session:spring-session-data-redis"
-    implementation "org.springframework.boot:spring-boot-starter-cache"
-    implementation "org.springframework.kafka:spring-kafka"
-    implementation "org.springframework.social:spring-social-web:${springSocialVersion}"
-    implementation "org.springframework.kafka:spring-kafka:${springKafkaVersion}"
+    implementation 'org.springframework.cloud:spring-cloud-starter'
+    implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
+    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'
+    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard'
 
-    implementation "org.springframework.cloud:spring-cloud-starter"
-    implementation "org.springframework.cloud:spring-cloud-starter-consul-discovery"
-    implementation "org.springframework.cloud:spring-cloud-starter-netflix-hystrix"
-    implementation "org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard"
-
+    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
     runtime("org.springframework.boot:spring-boot-devtools")
 
-    implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5"
+    implementation 'org.postgresql:postgresql:42.2.5'
+    implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
+    implementation 'com.jcabi:jcabi-manifests:1.1'
     implementation 'org.bitbucket.b_c:jose4j:0.6.5'
+    implementation 'io.github.microutils:kotlin-logging:1.6.26'
+    implementation 'org.slf4j:slf4j-parent:1.7.26'
     implementation 'com.github.penggle:kaptcha:2.3.2'
+    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
     implementation group: 'com.sun.jersey', name: 'jersey-client', version: '1.19'
     implementation group: 'javax.servlet', name: 'jstl', version: '1.2'
     implementation group: 'taglibs', name: 'standard', version: '1.1.2'
+    implementation group: 'commons-codec', name: 'commons-codec', version: '1.13'
     implementation files('libs/masmgc.sdk.sms-0.0.1-SNAPSHOT.jar')
+    //    implementation files('libs/ojdbc6.jar')
+    implementation 'commons-dbcp:commons-dbcp:1.4'
     implementation 'commons-beanutils:commons-beanutils:1.9.3'
-    implementation 'com.eatthepath:java-otp:0.1.0'
+
+    implementation 'log4j:log4j:1.2.17'
+    implementation 'com.alibaba:fastjson:1.2.60'
+
+    implementation 'com.eatthepath:java-otp:0.2.0'
     implementation project(':payapi-common')
+    /*支付宝SDK*/
+    implementation group: 'com.alipay.sdk', name: 'alipay-sdk-java', version: '3.7.110.ALL'
+
+    /*大理二维码jar*/
+    implementation 'com.supwisdom:dlsmk-qrcode:1.3.5'
+
+    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
+    annotationProcessor 'org.projectlombok:lombok:1.18.8'
+    compileOnly 'org.projectlombok:lombok:1.18.8'
 
     implementation "org.apache.commons:commons-lang3:${lang3Version}"
     implementation "net.javacrumbs.shedlock:shedlock-spring:${shedlockVersion}"
     implementation "net.javacrumbs.shedlock:shedlock-provider-redis-spring:${shedlockVersion}"
 
-    implementation "org.bitbucket.b_c:jose4j:${jose4jVersion}"
-    implementation files("libs/masmgc.sdk.sms-0.0.1-SNAPSHOT.jar")
-    implementation "commons-beanutils:commons-beanutils:${beanutilsVersion}"
-    /*支付宝SDK*/
-    implementation "com.alipay.sdk:alipay-sdk-java:${alipaySDKVersion}"
-
-    implementation "com.github.penggle:kaptcha:${kaptchaVersion}"
-    implementation "com.sun.jersey:jersey-client:${jerseyClientVersion}"
-
+    //    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
+    testImplementation 'org.springframework.boot:spring-boot-starter-test'
+    testImplementation 'io.rest-assured:rest-assured:3.3.0'
+    testImplementation 'io.rest-assured:spring-mock-mvc:3.3.0'
+    testImplementation 'org.hamcrest:hamcrest:2.1'
 }
 
 tasks.withType(JavaCompile) {
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java
index 79daf31..ed46360 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java
@@ -23,12 +23,13 @@
   public static final String YNRCC_SIGNKEY = "ynrcc.agent.signkey"; //农商行前置md5key
   public static final String YNRCC_MERCHANT_BANKCARDNO = "merchant.bankcardno";
   public static final String YNRCC_MERCHANT_BANKACCNAME = "merchant.bankaccname";
+  public static final String YNRCC_MERCHANT_BANKCARDNO_SEQNO = "merchant.bankcardno.seqno";
 
   public static final String PARAM_CONFIG_ERROR = "90000"; // 参数未配置
   public static final String PARAM_VALUE_ERROR = "90001"; // 参数值错误
 
-  public static final int AGENT_CONNECT_TIMEOUT = 10;
-  public static final int AGENT_READ_TIMEOUT = 10;
+  public static final int AGENT_CONNECT_TIMEOUT = 20;
+  public static final int AGENT_READ_TIMEOUT = 20;
   public static final String TRANSTYPE_SIGNCARD = "1"; //签约
   public static final String TRANSTYPE_UNSIGNCARD = "2"; //解约
 
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/UserSecretDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/UserSecretDao.java
new file mode 100644
index 0000000..22c5757
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/UserSecretDao.java
@@ -0,0 +1,10 @@
+package com.supwisdom.dlpay.api.dao;
+
+import com.supwisdom.dlpay.api.domain.TUserSecret;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+public interface UserSecretDao extends JpaRepository<TUserSecret, String> {
+  @Query("from TUserSecret a where a.uid=?1 ")
+  TUserSecret getByUid(String uid);
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TUserSecret.java b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TUserSecret.java
new file mode 100644
index 0000000..e5598e9
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TUserSecret.java
@@ -0,0 +1,66 @@
+package com.supwisdom.dlpay.api.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "tb_user_secret")
+public class TUserSecret {
+  @Id
+  @Column(name = "uid", nullable = false, length = 32)
+  private String uid;
+
+  @Column(name = "userid", length = 32)
+  private String userid;
+
+  @Column(name = "rsaprivate", length = 1000)
+  private String rsaprivate;
+
+  @Column(name = "rsapublic", length = 1000)
+  private String rsapublic;
+
+  @Column(name = "secertkey", length = 64)
+  private String secertkey;
+
+  public String getUid() {
+    return uid;
+  }
+
+  public void setUid(String uid) {
+    this.uid = uid;
+  }
+
+  public String getUserid() {
+    return userid;
+  }
+
+  public void setUserid(String userid) {
+    this.userid = userid;
+  }
+
+  public String getRsaprivate() {
+    return rsaprivate;
+  }
+
+  public void setRsaprivate(String rsaprivate) {
+    this.rsaprivate = rsaprivate;
+  }
+
+  public String getRsapublic() {
+    return rsapublic;
+  }
+
+  public void setRsapublic(String rsapublic) {
+    this.rsapublic = rsapublic;
+  }
+
+  public String getSecertkey() {
+    return secertkey;
+  }
+
+  public void setSecertkey(String secertkey) {
+    this.secertkey = secertkey;
+  }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/service/impl/SourceTypeServiceImpl.java b/payapi/src/main/java/com/supwisdom/dlpay/api/service/impl/SourceTypeServiceImpl.java
index 14631ee..62c471f 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/api/service/impl/SourceTypeServiceImpl.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/service/impl/SourceTypeServiceImpl.java
@@ -148,7 +148,7 @@
     if (!StringUtil.isEmpty(list)) {
       for (TSourceTypeConfig config : list) {
         if (config.getGlobalflag()) {
-          result.put(config.getConfigid(), config.getConfigValue()); //统用参数
+          result.put(config.getConfigid(), config.getConfigValue()); //通用参数
         }
       }
     }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/citizencard/dao/MonitorDlsmkCardpayDao.java b/payapi/src/main/java/com/supwisdom/dlpay/citizencard/dao/MonitorDlsmkCardpayDao.java
new file mode 100644
index 0000000..283cc05
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/citizencard/dao/MonitorDlsmkCardpayDao.java
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.citizencard.dao;
+
+import com.supwisdom.dlpay.citizencard.domain.TMonitorDlsmkCardpay;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface MonitorDlsmkCardpayDao extends JpaRepository<TMonitorDlsmkCardpay, String> {
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/citizencard/domain/TMonitorDlsmkCardpay.java b/payapi/src/main/java/com/supwisdom/dlpay/citizencard/domain/TMonitorDlsmkCardpay.java
new file mode 100644
index 0000000..6ffcaa3
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/citizencard/domain/TMonitorDlsmkCardpay.java
@@ -0,0 +1,64 @@
+package com.supwisdom.dlpay.citizencard.domain;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "TB_MONITOR_DLSMK_CARDPAY",
+    indexes = {@Index(name = "idx_monitor_dlsmk_cardpay", columnList = "request_url, createtime")})
+public class TMonitorDlsmkCardpay {
+  @Id
+  @Column(name = "refno", length = 32, nullable = false)
+  private String refno;
+
+  @Column(name = "use_milli_sec", precision = 9)
+  private Long useMilliSec;
+
+  @Column(name = "resp_json", length = 600)
+  private String respJson;
+
+  @Column(name = "request_url", length = 200)
+  private String requestUrl;
+
+  @Column(name = "createtime", length = 14)
+  private String createtime;
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  public Long getUseMilliSec() {
+    return useMilliSec;
+  }
+
+  public void setUseMilliSec(Long useMilliSec) {
+    this.useMilliSec = useMilliSec;
+  }
+
+  public String getRespJson() {
+    return respJson;
+  }
+
+  public void setRespJson(String respJson) {
+    this.respJson = respJson;
+  }
+
+  public String getRequestUrl() {
+    return requestUrl;
+  }
+
+  public void setRequestUrl(String requestUrl) {
+    this.requestUrl = requestUrl;
+  }
+
+  public String getCreatetime() {
+    return createtime;
+  }
+
+  public void setCreatetime(String createtime) {
+    this.createtime = createtime;
+  }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/dao/PointsDtlDao.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/dao/PointsDtlDao.java
index 5e3d58e..47f82c4 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/framework/dao/PointsDtlDao.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/dao/PointsDtlDao.java
@@ -19,8 +19,12 @@
 
   TPointsdtl findAllByUseridAndTransdateAndRefnoAndStatusAndFlagstatus(String userid,String transdate,Integer refno, String status,String flagstatus);
 
+  TPointsdtl findAllByUseridAndTransdateAndRefnoAndStatusAndFlagstatusAndType(String userid,String transdate,Integer refno, String status,String flagstatus,String type);
+
   TPointsdtl findAllByUseridAndTransdateStartingWithAndRefnoAndStatusAndFlagstatus(String userid,String transdate,Integer refno, String status,String flagstatus);
 
+  TPointsdtl findAllByUseridAndTransdateStartingWithAndRefnoAndStatusAndFlagstatusAndType(String userid,String transdate,Integer refno, String status,String flagstatus,String type);
+
   TPointsdtl findAllByUseridAndBillnoAndTypeAndRefno(String userid,String billno,String type,Integer refno);
 
   TPointsdtl findAllByUseridAndBillnoAndType(String userid,String billno,String type);
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/PointsServiceImpl.java b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/PointsServiceImpl.java
index 2d638b6..15c08ce 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/PointsServiceImpl.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/PointsServiceImpl.java
@@ -77,7 +77,7 @@
   @Override
   public LevelBean getLevel() {
     LevelBean levelBean = new LevelBean();
-    Sort sort = new Sort(Sort.Direction.ASC, "id");
+    Sort sort = Sort.by("id");
     List<TPersonLevel> list = personLevelDao.findAll(sort);
     if (!StringUtil.isEmpty(list)) levelBean.settPersonLevels(list);
     return levelBean;
@@ -197,7 +197,7 @@
 
   @Override
   public LevelBean getConpoints() {
-    Sort sort = new Sort(Sort.Direction.ASC, "id");
+    Sort sort = Sort.by("id");
     List<TConsumePoints> monthlist = consumePointsDao.findAllByAndRuletype(TradeDict.CONSUME_POINTS_MONTH, sort);
     List<TConsumePoints> daylist = consumePointsDao.findAllByAndRuletype(TradeDict.CONSUME_POINTS_DAY, sort);
     LevelBean levelBean = new LevelBean();
@@ -364,7 +364,7 @@
     List<TPersondtl> tPersondtl = getShopdtlByUseridAndTransdateAndReverseFlagAndStatus(userid, DateUtil.getNow("yyyyMMdd"), "none", "success");
     Double allConsume = -tPersondtl.stream().mapToDouble(o -> o.getAmount()).sum();
     // 2.循环日消费奖励规则
-    Sort sort = new Sort(Sort.Direction.ASC, "id");
+    Sort sort = Sort.by("id");
     List<TConsumePoints> daylist = consumePointsDao.findAllByAndRuletype(TradeDict.CONSUME_POINTS_DAY, sort);
     for (TConsumePoints temp : daylist) {
       // 3.判断用户是否达到日消费奖励积分的额度,达到再次判断是否获得过该规则的奖励
@@ -409,7 +409,7 @@
     List<TPersondtl> tPersondtl = getShopdtlByUseridAndTransdateStartingWithAndReverseFlagAndStatus(userid, DateUtil.getNow("yyyyMM"), "none", "success");
     Double allConsume = -tPersondtl.stream().mapToDouble(o -> o.getAmount()).sum();
     // 2.循环月消费奖励规则
-    Sort sort = new Sort(Sort.Direction.ASC, "id");
+    Sort sort = Sort.by("id");
     List<TConsumePoints> monthlist = consumePointsDao.findAllByAndRuletype(TradeDict.CONSUME_POINTS_MONTH, sort);
     for (TConsumePoints temp : monthlist) {
       // 3.判断用户是否达到月消费奖励积分的额度,达到再次判断是否获得过该规则的奖励
@@ -472,12 +472,12 @@
     List<TPersondtl> tPersondtl = getShopdtlByUseridAndTransdateAndReverseFlagAndStatus(userid, tPointsdtl.getTransdate(), "none", "success");
     Double allConsume = -tPersondtl.stream().mapToDouble(o -> o.getAmount()).sum();
     // 2.2.循环日消费奖励规则
-    Sort sort = new Sort(Sort.Direction.ASC, "id");
+    Sort sort = Sort.by("id");
     List<TConsumePoints> daylist = consumePointsDao.findAllByAndRuletype(TradeDict.CONSUME_POINTS_DAY, sort);
     for (TConsumePoints temp : daylist) {
       // 2.3.判断用户是否达到日消费奖励积分的额度,没有达到再次判断是否获得过该规则的奖励
       if (allConsume < Double.parseDouble(temp.getConsumeamount())) {
-        TPointsdtl tPointsdtltemp = pointsDtlDao.findAllByUseridAndTransdateAndRefnoAndStatusAndFlagstatus(userid, tPointsdtl.getTransdate(), temp.getId(),"init","in");
+        TPointsdtl tPointsdtltemp = pointsDtlDao.findAllByUseridAndTransdateAndRefnoAndStatusAndFlagstatusAndType(userid, tPointsdtl.getTransdate(), temp.getId(),"init","in",TradeDict.CONSUME_FLAG_DAY);
         if (tPointsdtltemp != null) {
           tPointsdtltemp.setStatus("refund");
           pointsDtlDao.save(tPointsdtltemp);
@@ -502,7 +502,7 @@
     for (TConsumePoints temp : monthlist) {
       // 3.3.判断用户是否达到月消费奖励积分的额度,没有达到再次判断是否获得过该规则的奖励
       if (allConsumeMonth < Double.parseDouble(temp.getConsumeamount())) {
-        TPointsdtl tPointsdtltemp = pointsDtlDao.findAllByUseridAndTransdateStartingWithAndRefnoAndStatusAndFlagstatus(userid, tPointsdtl.getTransdate(), temp.getId(),"init","in");
+        TPointsdtl tPointsdtltemp = pointsDtlDao.findAllByUseridAndTransdateStartingWithAndRefnoAndStatusAndFlagstatusAndType(userid, tPointsdtl.getTransdate(), temp.getId(),"init","in",TradeDict.CONSUME_FLAG_MONTH);
         if (tPointsdtltemp != null) {
           tPointsdtltemp.setStatus("refund");
           pointsDtlDao.save(tPointsdtltemp);
@@ -587,7 +587,7 @@
   }
 
   public TPersonLevel getLevelByPoints(Integer points) {
-    Sort sort = new Sort(Sort.Direction.DESC, "id");
+    Sort sort = Sort.by(Sort.Order.desc("id"));
     List<TPersonLevel> all = personLevelDao.findAll(sort);
     for (TPersonLevel temp : all) {
       if (points >= temp.getPointsLower()) {
@@ -763,7 +763,7 @@
       personLevelResult.setPointsName("消费积分");
       levelBeans.add(personLevelResult);
       //统计日累计消费积分
-      Sort sort = new Sort(Sort.Direction.ASC, "id");
+      Sort sort = Sort.by("id");
       List<TConsumePoints> daylist = consumePointsDao.findAllByAndRuletype(TradeDict.CONSUME_POINTS_DAY, sort);
       List<Integer> dayTemp = daylist.stream().map(TConsumePoints::getId).collect(Collectors.toList());
       LevelBean dayTempBean = new LevelBean();
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/UserDataServiceImpl.java b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/UserDataServiceImpl.java
index a7ed10e..33d8df2 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/UserDataServiceImpl.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/UserDataServiceImpl.java
@@ -196,7 +196,9 @@
 
   @Override
   public PageResult<CitizenCardShowBean> getUserCitizenCardPage(CitizenCardSearchBean param, int pageNo, int pageSize) {
-    StringBuffer querySql = new StringBuffer("select t.id as cid,t.cardno,t.cardtype,t.cardphyid,case when t.status='closed' then 'closed' else t.trans_status end as status,t.expiredate,a.id as bkid,a.cardno as bankcardno,a.signed,t.lastsaved,t.userid,p.name as username,p.idtype,p.idno,p.mobile,p.email,p.sex,t.bus_card_type as buscardtype \n" +
+    StringBuffer querySql = new StringBuffer("select t.id as cid,t.cardno,t.cardtype,t.cardphyid, \n" +
+        "case when t.status='closed' or a.status='closed' then 'closed' when t.status='normal' and a.status='normal' and t.trans_status='normal' and a.trans_status='normal' then 'normal' when t.trans_status='normal' then a.trans_status else t.trans_status end as status, \n" +
+        "t.expiredate,a.id as bkid,a.cardno as bankcardno,a.signed,t.lastsaved,t.userid,p.name as username,p.idtype,p.idno,p.mobile,p.email,p.sex,t.bus_card_type as buscardtype \n" +
         "from tb_card t left join tb_card a on a.cardtype='bankcard' and a.cardphyid=t.cardphyid and a.userid=t.userid left join tb_person p on p.userid=t.userid \n" +
         "where t.cardtype='citizencard' ");
     StringBuffer countSql = new StringBuffer("select count(t.id) as cnt \n" +
@@ -313,7 +315,7 @@
       if (!StringUtil.isEmpty(cardList)) throw new WebCheckException("物理卡号已经存在!");
 
       TPerson person = personDao.findByIdentity(idtype.trim(), idno.trim());
-      if (null != person && username.trim().equals(person.getName()))
+      if (null != person && !username.trim().equals(person.getName()))
         throw new WebCheckException("证件号对应的用户已经存在,且姓名不匹配!");
 
       SystemDateTime dt = systemUtilService.getSysdatetime();
@@ -394,9 +396,10 @@
       bankCard.setCardtype(ConstantUtil.CARDTYPE_BANKCARD);
       bankCard.setCardphyid(cardphyid.trim());
       bankCard.setStatus(TradeDict.STATUS_NORMAL);
-      bankCard.setTransStatus(TradeDict.STATUS_NORMAL);
+      bankCard.setTransStatus(cardstatus.trim());
       if (TradeDict.STATUS_CLOSED.equals(cardstatus.trim())) {
         bankCard.setStatus(TradeDict.STATUS_CLOSED);
+        bankCard.setTransStatus(TradeDict.STATUS_ABNORMAL);
       }
       bankCard.setExpiredate("21991231");
       bankCard.setSigned("1".equals(signstatus.trim()));
@@ -535,6 +538,7 @@
           cardUpdate = true;
         }
       } else if (!cardstatus.trim().equals(cityCard.getTransStatus())) {
+        cityCard.setStatus(TradeDict.STATUS_NORMAL);
         cityCard.setTransStatus(cardstatus.trim());
         cardUpdate = true;
       }
@@ -562,10 +566,17 @@
         bankCard.setCardphyid(cityCard.getCardphyid());
         bankcardUpdate = true;
       }
-      if(TradeDict.STATUS_CLOSED.equals(cardstatus.trim()) && !TradeDict.STATUS_CLOSED.equals(bankCard.getStatus())){
-        bankCard.setStatus(TradeDict.STATUS_CLOSED);
+      if (TradeDict.STATUS_CLOSED.equals(cardstatus.trim())) {
+        if (!TradeDict.STATUS_CLOSED.equals(bankCard.getStatus())) {
+          bankCard.setStatus(TradeDict.STATUS_CLOSED);
+          bankcardUpdate = true;
+        }
+      } else if (!cardstatus.trim().equals(bankCard.getTransStatus())) {
+        bankCard.setStatus(TradeDict.STATUS_NORMAL);
+        bankCard.setTransStatus(cardstatus.trim());
         bankcardUpdate = true;
       }
+
       if(!bankCard.getSigned().equals("1".equals(signstatus.trim()))){
         bankCard.setSigned("1".equals(signstatus.trim()));
         bankcardUpdate = true;
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/citizencard_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/citizencard_service.kt
index 526d447..b5c246d 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/citizencard_service.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/citizencard_service.kt
@@ -9,6 +9,7 @@
 import com.supwisdom.dlpay.agent.citizencard.YnrccUtil
 import com.supwisdom.dlpay.agent.domain.QrcodePayTrans
 import com.supwisdom.dlpay.api.domain.TTransactionMain
+import com.supwisdom.dlpay.api.service.TransactionMonitorService
 import com.supwisdom.dlpay.framework.util.MoneyUtil
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.stereotype.Component
@@ -37,6 +38,9 @@
     @Autowired
     private lateinit var citizencardPayService: CitizencardPayService
 
+    @Autowired
+    private lateinit var transactionMonitorService: TransactionMonitorService
+
     private fun agentCode(code: String, msg: String?): org.springframework.data.util.Pair<AgentCode, YnrccRespCode> {
         return YnrccUtil.errcode.firstOrNull {
             it.second.code == code
@@ -55,8 +59,11 @@
     }
 
     override fun pay(transaction: TTransactionMain): AgentResponse<DtlStatus> {
+        val start = System.currentTimeMillis()
         val resp = citizencardPayService.cardPay(transaction.shopDtl.shopaccno, transaction.personDtl.userid, transaction.accdate,
                 Math.abs(MoneyUtil.YuanToFen(transaction.personDtl.amount)), transaction.dtltype, transaction.refno)
+        val end = System.currentTimeMillis()
+        transactionMonitorService.doMonitorCitizenCardPay(transaction.refno, end - start, resp)
 
         return AgentResponse<DtlStatus>().also {
             val code = agentCode(resp.code, resp.message)
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/impl/citizencard_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/impl/citizencard_service_impl.kt
index 0d97656..7756d8c 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/impl/citizencard_service_impl.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/impl/citizencard_service_impl.kt
@@ -170,7 +170,8 @@
         }
         val merchantBankcardno = config[YnrccUtil.YNRCC_MERCHANT_BANKCARDNO]
         val merchantBankaccname = config[YnrccUtil.YNRCC_MERCHANT_BANKACCNAME]
-        if (StringUtil.isEmpty(merchantBankcardno) || StringUtil.isEmpty(merchantBankaccname)) {
+        val merchantBankcardnoSeqno = config[YnrccUtil.YNRCC_MERCHANT_BANKCARDNO_SEQNO]
+        if (StringUtil.isEmpty(merchantBankcardno) || StringUtil.isEmpty(merchantBankaccname) || StringUtil.isEmpty(merchantBankcardnoSeqno)) {
             resp.code = YnrccUtil.PARAM_CONFIG_ERROR
             resp.message = "系统参数未配置[商户收款银行账号]"
             logger.error(resp.message)
@@ -190,7 +191,7 @@
             resp.message = "用户[${person.userid}]未绑定银行卡"
             logger.error(resp.message)
             return resp
-        } else if (TradeDict.STATUS_NORMAL != userBankcard.status) {
+        } else if (TradeDict.STATUS_NORMAL != userBankcard.transStatus) {
             resp.code = "99"
             resp.message = "用户[${person.userid}]绑定银行卡状态异常"
             logger.error(resp.message)
@@ -216,6 +217,7 @@
         params["idno"] = person.idno
         params["merchant_bankcardno"] = merchantBankcardno!!
         params["merchant_bankaccname"] = merchantBankaccname!!
+        params["merchant_bankcardno_seqno"] = merchantBankcardnoSeqno!!
         params["amount"] = amount.toString()
         params["scenario"] = scenario
         params["description"] = "市民卡代扣消费"
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
index 87e054c..692be0c 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
@@ -11,6 +11,7 @@
 import com.supwisdom.dlpay.api.bean.groups.InitAction
 import com.supwisdom.dlpay.api.domain.TSourceType
 import com.supwisdom.dlpay.api.service.*
+import com.supwisdom.dlpay.api.util.Constants
 import com.supwisdom.dlpay.exception.TransactionCheckException
 import com.supwisdom.dlpay.framework.ResponseBodyBuilder
 import com.supwisdom.dlpay.framework.service.SystemUtilService
@@ -780,12 +781,12 @@
      * */
     @PostMapping("/qrcodequery")
     fun qrcodeQuery(@RequestBody param: DoorQRCodeParam): ResponseEntity<Any> {
-        val token = redisTemplate.opsForValue().get(param.qrcode)
-        if (token.isNullOrEmpty()) {
-            return ResponseEntity.ok(ResponseBodyBuilder.create()
-                    .fail(1, "二维码不存在或已失效"))
-        }
-        val ret = qrCodeService.decodeCode(token)
+//        val token = redisTemplate.opsForValue().get(param.qrcode)
+//        if (token.isNullOrEmpty()) {
+//            return ResponseEntity.ok(ResponseBodyBuilder.create()
+//                    .fail(1, "二维码不存在或已失效"))
+//        }
+        val ret = qrCodeService.decodeCode(param.qrcode)
         return if (ret.retcode == 0) {
             ResponseEntity.ok(ResponseBodyBuilder.create()
                     .success(ret, "成功"))
@@ -794,4 +795,48 @@
                     .fail(ret.retcode, ret.retmsg))
         }
     }
+
+    /**
+     * ============================================================================
+     *                            获取市民卡二维码key
+     * ============================================================================
+     * */
+    @PostMapping("/citizencard/qrcodekey")
+    fun queryQrcodeKey(@RequestBody param: CitizenQrcodeKeyParam): ResponseEntity<Any> {
+        val ret = qrCodeService.queryQRCodeKeys(param.timestamp)
+        return if (ret.retcode == 0) {
+            ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .success(ret, "成功"))
+        } else {
+            ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(ret.retcode, ret.retmsg))
+        }
+    }
+
+    /**
+     * ============================================================================
+     *                            市民卡二维码交易初始化
+     * ============================================================================
+     * */
+    @PostMapping("/citizencard/qrcodepayinit")
+    fun citizencardQrcodePayinit(@Valid @RequestBody param: CitizenQrcodePayinitParam): ResponseEntity<Any> {
+        val pInfo = qrCodeService.decodeCode(param.qrcode, "online" == param.qrcodeType, param.cardNo, param.tac, param.amount, param.transdate, param.transtime)
+        if (pInfo.retcode != 0) {
+            //二维码解析错误
+            return ResponseEntity.ok(ResponseBodyBuilder.create().fail(pInfo.retcode, pInfo.retmsg))
+        }
+
+        //直接走 市民卡[交易初始化] 接口
+        return citizencardPayinit(CitizenCardPayinitParam().apply {
+            cardNo = pInfo.citycardno
+            shopaccno = param.shopaccno
+            amount = param.amount
+            billno = param.billno
+            transdate = param.transdate
+            transtime = param.transtime
+            dtltype = param.dtltype
+        })
+    }
+
+
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/user_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/user_api_controller.kt
index 2c80fa3..047cf60 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/user_api_controller.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/user_api_controller.kt
@@ -537,10 +537,14 @@
         }
         if (resp.sinstatus == YnrccUtil.TRANSTYPE_SIGNCARD ) {
             signed = TradeDict.STATUS_YES
-            if(!card.signed){
-                card.signed = true
-                mobileApiService.saveCard(card)
-            }
+            //绑卡返回已签约,保存UserSecret
+            mobileApiService.signBxy(card,SignBxyParam().apply {
+                this.userid = person.userid
+                this.uid = param.uid
+                this.secertkey = param.secertkey
+                this.rsapublic = param.rsapublic
+                this.rsaprivate = param.rsaprivate
+            })
         }
         return  ResponseEntity.ok(ResponseBodyBuilder.create().data("signed", signed)
                 .success("ok"))
@@ -579,19 +583,18 @@
      * 签约银行协议
      */
     @PostMapping("/signbxy")
-    fun signbxy(userid: String, code: String, phone: String): ResponseEntity<Any> {
-        val card = mobileApiService.findCardByUserid(userid)
+    fun signbxy(@RequestBody param:SignBxyParam): ResponseEntity<Any> {
+        val card = mobileApiService.findCardByUserid(param.userid)
                 ?: return ResponseEntity.ok(ResponseBodyBuilder.create()
                         .fail(400, "卡片不存在,请重新绑定"))
         //call sign api
         val person = userService.findOnePersonByUserid(card.userid)
-        val resp = citizencardPayService.signCard(card.cardno, person.name, person.idtype, person.idno, phone, YnrccUtil.TRANSTYPE_SIGNCARD, code)
+        val resp = citizencardPayService.signCard(card.cardno, person.name, person.idtype, person.idno, param.phone, YnrccUtil.TRANSTYPE_SIGNCARD, param.code)
         if (resp.code != "0000") {
             return ResponseEntity.ok(ResponseBodyBuilder.create()
                     .fail(500, resp.message))
         }
-        card.signed = true
-        mobileApiService.saveCard(card)
+        mobileApiService.signBxy(card,param)
         return ResponseEntity.ok(ResponseBodyBuilder.create()
                 .success("ok"))
     }
@@ -647,4 +650,19 @@
                     .fail(400,"未完成首次消费任务"))
         }
     }
+
+    /**
+     * 根据手机用户uid查询市民卡信息
+     * */
+    @PostMapping("/queryCitizenCard")
+    fun queryCitizenCard(uid: String): ResponseEntity<Any> {
+        val ret = mobileApiService.findCardInfoByMobileUid(uid)
+        return if (ret.retcode == 0) {
+            ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .success(ret, "成功"))
+        } else {
+            ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(ret.retcode, ret.retmsg))
+        }
+    }
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/card_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/card_service.kt
index e99baab..5c60767 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/card_service.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/card_service.kt
@@ -33,4 +33,9 @@
 
     @Transactional(rollbackFor = arrayOf(Exception::class))
     fun updateCardTransStatus(cardno:String,status:String): ApiResponse
+
+    @Transactional(rollbackFor = arrayOf(Exception::class), readOnly = true)
+    fun findCardByUseridAndCardphyid(userid: String, cardtype: String, cardphyid: String?): TCard?
+
+    fun findCardByCardnoAndCardtype(cardno: String, cardtype: String): TCard?
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/card_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/card_service_impl.kt
index 6c67732..34e3cbf 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/card_service_impl.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/card_service_impl.kt
@@ -26,10 +26,13 @@
 class CardServiceImpl : CardService {
     @Autowired
     lateinit var cardDao: CardDao
+
     @Autowired
     lateinit var personDao: PersonDao
+
     @Autowired
-    lateinit var citizencardLossApplyDao:CitizencardLossApplyDao
+    lateinit var citizencardLossApplyDao: CitizencardLossApplyDao
+
     @Autowired
     lateinit var systemUtilService: SystemUtilService
 
@@ -125,8 +128,8 @@
         query.unwrap(NativeQueryImpl::class.java).setResultTransformer(Transformers.aliasToBean(CitizenCardInfo::class.java))
         @Suppress("UNCHECKED_CAST")
         val list = query.resultList as List<CitizenCardInfo>
-        resp.retcode=0
-        resp.retmsg="OK"
+        resp.retcode = 0
+        resp.retmsg = "OK"
         resp.cards = list
         return resp
     }
@@ -173,7 +176,7 @@
     override fun getCardByCardNoOrUserid(param: QueryCardParam): TCard? {
         if (!StringUtil.isEmpty(param.cardno)) {
             return cardDao.findCardByCardnoAndCardtype(param.cardno, param.cardtype)
-        }else if (!StringUtil.isEmpty(param.userid)) {
+        } else if (!StringUtil.isEmpty(param.userid)) {
             return cardDao.findCardByUseridAndCardtype(param.userid, param.cardtype)
         }
         return null
@@ -224,4 +227,16 @@
             this.retmsg = "ok"
         }
     }
+
+    override fun findCardByUseridAndCardphyid(userid: String, cardtype: String, cardphyid: String?): TCard? {
+        return if (cardphyid.isNullOrEmpty()) {
+            cardDao.findCardByUseridAndCardtype(userid, cardtype)
+        } else {
+            cardDao.findBankcardByCitizencard(userid, cardtype, cardphyid)
+        }
+    }
+
+    override fun findCardByCardnoAndCardtype(cardno: String, cardtype: String): TCard? {
+        return cardDao.findCardByCardnoAndCardtype(cardno, cardtype)
+    }
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt
index 38b6374..a55d1e9 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt
@@ -2,13 +2,16 @@
 
 import com.google.gson.Gson
 import com.supwisdom.dlpay.api.bean.ApiResponse
+import com.supwisdom.dlpay.api.bean.CitizenQrcodeKey
 import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse
 import com.supwisdom.dlpay.api.bean.QrcodeParam
 import com.supwisdom.dlpay.api.service.CardService
 import com.supwisdom.dlpay.api.service.QRCodeService
 import com.supwisdom.dlpay.api.service.UserService
+import com.supwisdom.dlpay.busqrcode.QrCode
 import com.supwisdom.dlpay.framework.service.SystemUtilService
-import com.supwisdom.dlpay.framework.util.MD5
+import com.supwisdom.dlpay.framework.util.DateUtil
+import com.supwisdom.dlpay.framework.util.StringUtil
 import com.supwisdom.dlpay.framework.util.TradeDict
 import com.supwisdom.dlpay.mobile.service.MobileApiService
 import com.supwisdom.dlpay.util.*
@@ -17,6 +20,7 @@
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.data.redis.core.RedisTemplate
 import org.springframework.stereotype.Service
+import com.supwisdom.dlpay.framework.util.MD5
 import java.time.Duration
 
 @Service
@@ -34,22 +38,46 @@
 
     val logger = KotlinLogging.logger { }
 
-    override fun encodeCode(uid:String): ApiResponse {
+    private fun qrcodeConfig(): Map<String, String> {
+        var step = "5"
+        var offset = "3"
+        var debug = "false"
+        var offlineQrcode = "false"
+        val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey")?.trim() ?: ""
+        val iv = systemUtilService.getBusinessValue("aes.cfb.iv")?.trim() ?: ""
+        val prefix = systemUtilService.getBusinessValue("dlsmk.qrcode.prefix")?.trim() ?: ""
+        val totpStep = systemUtilService.getBusinessValue("dlsmk.qrcode.totp.step")  //fixme: jar包写死了5s
+        val totpOffset = systemUtilService.getBusinessValue("dlsmk.qrcode.totp.offset")
+        val debugEnable = systemUtilService.getBusinessValue("dlsmk.qrcode.log.debug")
+        val newQrcode = systemUtilService.getBusinessValue("dlsmk.qrcode.offline.enable")
+        if (NumberUtils.isDigits(totpStep)) step = totpStep.trim()
+        if (NumberUtils.isDigits(totpOffset)) offset = totpOffset.trim()
+        if ("true".equals(debugEnable, true)) debug = "true"
+        if ("true".equals(newQrcode, true)) offlineQrcode = "true"
+        return mapOf("rootkey" to rootkey, "iv" to iv, "prefix" to prefix, "step" to step, "offset" to offset, "debug" to debug, "offline" to offlineQrcode)
+    }
+
+    override fun encodeCode(uid: String): ApiResponse {
         var resp = ApiResponse()
-        val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey")
-        val iv = systemUtilService.getBusinessValue("aes.cfb.iv")
-        if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) {
+        val config = qrcodeConfig()
+        val rootkey = config["rootkey"]
+        val iv = config["iv"]
+        val prefix = config["prefix"] ?: ""
+        val debug = "true" == config["debug"]
+        val offlineQRCode = "true" == config["offline"]
+
+        if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) {
             resp.retcode = 1
             resp.retmsg = "秘钥未配置"
             return resp
         }
         val muser = mobileApiService.findUserById(uid)
-        if(muser==null||TradeDict.STATUS_NORMAL!=muser.status){
+        if (muser == null || TradeDict.STATUS_NORMAL != muser.status) {
             resp.retcode = 1
             resp.retmsg = "用户不存在或状态异常"
             return resp
         }
-        if(muser.userid.isNullOrEmpty()){
+        if (muser.userid.isNullOrEmpty()) {
             resp.retcode = 1
             resp.retmsg = "用户未绑定身份"
             return resp
@@ -63,142 +91,477 @@
             resp.retcode = 1
             resp.retmsg = "银行卡未签约"
             return resp
+        } else if (TradeDict.STATUS_NORMAL != bankCard.transStatus) {
+            resp.retcode = 1
+            resp.retmsg = when (bankCard.transStatus) {
+                TradeDict.STATUS_UNUSE -> "银行卡未启用!"
+                TradeDict.STATUS_LOST -> "银行卡已挂失!"
+                TradeDict.STATUS_LOCKED -> "银行卡已锁定!"
+                TradeDict.STATUS_FROZEN -> "银行卡已冻结!"
+                else -> "银行卡状态异常!"
+            }
+            return resp
         }
 
-        val totp = QrCodeTotpUtil.generateTOTP(muser.secertkey)
-        val rowdata = QrcodeRawData()
-        rowdata.userid = muser.userid
-        rowdata.setTotp(totp)
-        val orgData = Gson().toJson(rowdata)
-        val publicKey = RSAKeysGenerate.getPublicKey(muser.rsapublic)
-        val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
-        val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
+        val cityCard = cardService.findCardByUseridAndCardphyid(bankCard.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankCard.cardphyid)
+        if (null == cityCard) {
+            resp.retcode = 1
+            resp.retmsg = "用户未绑定市民卡"
+            return resp
+        } else if (TradeDict.STATUS_NORMAL != cityCard.status) {
+            resp.retcode = 1
+            resp.retmsg = "市民卡已注销"
+            return resp
+        } else if (TradeDict.STATUS_NORMAL != cityCard.transStatus) {
+            resp.retcode = 1
+            resp.retmsg = when (cityCard.transStatus) {
+                TradeDict.STATUS_UNUSE -> "市民卡未启用!"
+                TradeDict.STATUS_LOST -> "市民卡已挂失!"
+                TradeDict.STATUS_LOCKED -> "市民卡已锁定!"
+                TradeDict.STATUS_FROZEN -> "市民卡已冻结!"
+                else -> "市民卡状态异常!"
+            }
+            return resp
+        }
 
-        val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
-        redisTemplate.opsForValue().set(key,qrcode, Duration.ofSeconds(20))
-        resp.retcode = 0
-        resp.retmsg = key
-        return resp
+        if (offlineQRCode) {
+            //脱机码,新版本统一码
+            try {
+                val handle = QrCode.builder()
+                        .rootKey(rootkey)
+                        .iv(iv)
+                        .uid(uid)
+                        .card(cityCard.cardno, cityCard.busCardType ?: "80")
+                        .prefix(prefix)
+                        .debug(debug)
+                        .create()
+                val qrcode = handle.qrcode()
+                resp.retcode = 0
+                resp.retmsg = qrcode
+                return resp
+            } catch (ex: Exception) {
+                ex.printStackTrace()
+                resp.retcode = 1
+                resp.retmsg = "二维码生成失败!${ex.message}"
+                return resp
+            }
+
+        } else {
+            //老版本H5码,存redis
+            val totp = QrCodeTotpUtil.generateTOTP(muser.secertkey)
+            val rowdata = QrcodeRawData()
+            rowdata.userid = muser.userid
+            rowdata.setTotp(totp)
+            val orgData = Gson().toJson(rowdata)
+            val publicKey = RSAKeysGenerate.getPublicKey(muser.rsapublic)
+            val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
+            val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
+
+            val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
+            redisTemplate.opsForValue().set(key, qrcode, Duration.ofSeconds(20))
+
+            resp.retcode = 0
+            resp.retmsg = key
+            return resp
+        }
+
     }
 
     override fun encodeCode(param: QrcodeParam): ApiResponse {
-        var resp = ApiResponse()
+        val resp = ApiResponse()
         val uid = param.uid
-        val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey")
-        val iv = systemUtilService.getBusinessValue("aes.cfb.iv")
-        if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) {
+        val config = qrcodeConfig()
+        val rootkey = config["rootkey"]
+        val iv = config["iv"]
+        val prefix = config["prefix"] ?: ""
+        val debug = "true" == config["debug"]
+        val offlineQRCode = "true" == config["offline"]
+
+        if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) {
             resp.retcode = 1
             resp.retmsg = "秘钥未配置"
             return resp
         }
-        val totp = QrCodeTotpUtil.generateTOTP(param.secertkey)
-        val rowdata = QrcodeRawData()
-        rowdata.userid = param.userid
-        rowdata.setTotp(totp)
-        val orgData = Gson().toJson(rowdata)
-        val publicKey = RSAKeysGenerate.getPublicKey(param.rsapublic)
-        val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
-        val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
 
-        val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
-        redisTemplate.opsForValue().set(key,qrcode, Duration.ofSeconds(20))
-        resp.retcode = 0
-        resp.retmsg = key
-        return resp
-    }
-
-    override fun decodeCode(qrcode :String): DoorQrcodeResponse {
-
-        //解密
-        var resp = DoorQrcodeResponse()
-        try {
-            val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey")
-            val iv = systemUtilService.getBusinessValue("aes.cfb.iv")
-            val totpoffset = systemUtilService.getBusinessValue("aes.cfb.totp.offset")
-            var offset = 3
-            if (NumberUtils.isDigits(totpoffset)) {
-                offset = Integer.valueOf(totpoffset).toInt()
-            }
-            if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) {
-                resp.retcode = 1
-                resp.retmsg = "秘钥未配置"
-                return resp
-            }
-
-            println("vtoken=[" + qrcode + "],length=[" + qrcode.length + "]")
-            val encdataBack = AesUtil.decryptCFB(qrcode, rootkey, iv, "AES/CFB/NoPadding")
-            if (encdataBack == null) {
-                resp.retcode = 1
-                resp.retmsg = "二维码解析失败"
-                return resp
-            }
-            val uid = encdataBack.substring(0, encdataBack.indexOf(":"))
-            val muser = mobileApiService.findUserById(uid)
-            if(muser==null||TradeDict.STATUS_NORMAL!=muser.status){
-                resp.retcode = 1
-                resp.retmsg = "用户不存在或状态异常"
-                return resp
-            }
-            val seed32 = muser.secertkey
-            val rawEncdata = encdataBack.substring(encdataBack.indexOf(":") + 1)
-
-            val privateKey = RSAKeysGenerate.getPrivateKey(muser.rsaprivate)
-            val rawdataBack = RSAKeysGenerate.decryptbyte(privateKey, rawEncdata)
-            logger.info("rawdata_back=$rawdataBack")
-
-            val rawData = Gson().fromJson(rawdataBack, QrcodeRawData::class.java)
-            logger.info("userid=" + rawData.userid)
-            if (null == rawData || rawData.userid.isNullOrEmpty()) {
-                resp.retcode = 1
-                resp.retmsg = "用户不存在"
-                return resp
-            }
-            val person = userService.findOnePersonByUserid(rawData.userid)
-
-            val bankcard = mobileApiService.findCardByUserid(person.userid)
-            if (null == bankcard) {
-                resp.retcode = 9
-                resp.retmsg = "二维码识别错误,手机绑定的银行卡已注销"
-                return resp
-            }
-            //银行卡必定存在对应的市民卡
-            val citycard = mobileApiService.findCardByUseridAndCardphyid(person.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankcard.cardphyid)
-            if (null == citycard) {
-                resp.retcode = 8
-                resp.retmsg = "二维码识别异常"
-                return resp
-            }
-
-            val verifyBarcode = QrCodeTotpUtil.verifyCode(rawData.totp, seed32, offset)
-            return if (verifyBarcode) {
-                resp.retcode = 0
-                resp.retmsg = "OK"
-                resp.userid = person.userid
-                resp.username = person.name
-                resp.sex = person.sex
-                resp.idtype = person.idtype
-                resp.idno = person.idno
-                resp.phone = person.mobile
-
-                resp.citycardno = citycard.cardno
-                resp.cardphyid = citycard.cardphyid
-                resp.expiredate = citycard.expiredate
-                resp.cardstatus = citycard.status
-                resp.transstatus = citycard.transStatus
-                resp.bankcardno = bankcard.cardno
-                resp
-            } else {
-                resp.retcode = 1
-                resp.retmsg = "二维码校验失败"
-                resp
-            }
-        } catch (e: Exception) {
-            e.printStackTrace()
+        val userSec = mobileApiService.findUserSecretByUid(uid)
+        if (null == userSec) {
             resp.retcode = 1
-            resp.retmsg = "二维码识别异常"
+            resp.retmsg = "识别手机用户身份失败!"
+            return resp
+        } else if (userSec.userid != param.userid) {
+            resp.retcode = 1
+            resp.retmsg = "手机用户身份识别异常!"
+            return resp
+        }
+        val bankCard = cardService.getBankcardByUserid(param.userid)
+        if (null == bankCard) {
+            resp.retcode = 1
+            resp.retmsg = "用户未绑定银行卡"
+            return resp
+        } else if (!bankCard.signed) {
+            resp.retcode = 1
+            resp.retmsg = "银行卡未签约"
+            return resp
+        } else if (TradeDict.STATUS_NORMAL != bankCard.transStatus) {
+            resp.retcode = 1
+            resp.retmsg = when (bankCard.transStatus) {
+                TradeDict.STATUS_UNUSE -> "银行卡未启用!"
+                TradeDict.STATUS_LOST -> "银行卡已挂失!"
+                TradeDict.STATUS_LOCKED -> "银行卡已锁定!"
+                TradeDict.STATUS_FROZEN -> "银行卡已冻结!"
+                else -> "银行卡状态异常!"
+            }
             return resp
         }
 
+        val cityCard = cardService.findCardByUseridAndCardphyid(bankCard.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankCard.cardphyid)
+        if (null == cityCard) {
+            resp.retcode = 1
+            resp.retmsg = "用户未绑定市民卡"
+            return resp
+        } else if (TradeDict.STATUS_NORMAL != cityCard.status) {
+            resp.retcode = 1
+            resp.retmsg = "市民卡已注销"
+            return resp
+        } else if (TradeDict.STATUS_NORMAL != cityCard.transStatus) {
+            resp.retcode = 1
+            resp.retmsg = when (cityCard.transStatus) {
+                TradeDict.STATUS_UNUSE -> "市民卡未启用!"
+                TradeDict.STATUS_LOST -> "市民卡已挂失!"
+                TradeDict.STATUS_LOCKED -> "市民卡已锁定!"
+                TradeDict.STATUS_FROZEN -> "市民卡已冻结!"
+                else -> "市民卡状态异常!"
+            }
+            return resp
+        }
+
+        if (offlineQRCode) {
+            //脱机码,新版本统一码
+            try {
+                val handle = QrCode.builder()
+                        .rootKey(rootkey)
+                        .iv(iv)
+                        .uid(uid)
+                        .card(cityCard.cardno, cityCard.busCardType ?: "80")
+                        .prefix(prefix)
+                        .debug(debug)
+                        .create()
+                val qrcode = handle.qrcode()
+                resp.retcode = 0
+                resp.retmsg = qrcode
+                return resp
+            } catch (ex: Exception) {
+                ex.printStackTrace()
+                resp.retcode = 1
+                resp.retmsg = "二维码生成失败!${ex.message}"
+                return resp
+            }
+
+        } else {
+            //老版本H5码,存redis
+            val totp = QrCodeTotpUtil.generateTOTP(userSec.secertkey)
+            val rowdata = QrcodeRawData()
+            rowdata.userid = userSec.userid
+            rowdata.setTotp(totp)
+            val orgData = Gson().toJson(rowdata)
+            val publicKey = RSAKeysGenerate.getPublicKey(userSec.rsapublic)
+            val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
+            val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
+
+            val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
+            redisTemplate.opsForValue().set(key, qrcode, Duration.ofSeconds(20))
+
+            resp.retcode = 0
+            resp.retmsg = key
+            return resp
+        }
+
+    }
+
+    override fun decodeCode(qrcode: String): DoorQrcodeResponse {
+        val resp = DoorQrcodeResponse()
+        val config = qrcodeConfig()
+        val rootkey = config["rootkey"]
+        val iv = config["iv"]
+        val prefix = config["prefix"] ?: ""
+        val debug = "true" == config["debug"]
+        val offlineQRCode = "true" == config["offline"]
+        var offset = 3
+        if (offlineQRCode) {
+            val totpoffset = config["offset"]
+            if (NumberUtils.isDigits(totpoffset)) {
+                offset = totpoffset!!.toInt()
+            }
+        } else {
+            val totpoffset = systemUtilService.getBusinessValue("aes.cfb.totp.offset")
+            if (NumberUtils.isDigits(totpoffset)) {
+                offset = Integer.valueOf(totpoffset).toInt()
+            }
+        }
+
+        if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) {
+            resp.retcode = 1
+            resp.retmsg = "秘钥未配置"
+            return resp
+        }
+        println("vtoken=[" + qrcode + "],length=[" + qrcode.length + "]")
+
+        //解密
+        if(offlineQRCode){
+            //脱机码,新版本统一码
+            var data = HashMap<String, String>(0)
+            try {
+                val handle = QrCode.builder().rootKey(rootkey).iv(iv).prefix(prefix).debug(debug).create()
+                //fixme:解码已校验totp
+                data = handle.decodeWithTotpCheck(qrcode, offset) as HashMap<String, String>
+            } catch (ex: Exception) {
+                ex.printStackTrace()
+                resp.retcode = 1
+                resp.retmsg = "二维码解析报错,${ex.message}"
+                return resp
+            }
+            val uid = data[QrCode.FIELD_UID]
+            val cardno = data[QrCode.FIELD_CARDNO]
+            val cardtype = data[QrCode.FIELD_CARDTYPE] //公交卡类型
+            if (uid.isNullOrEmpty() || cardno.isNullOrEmpty() || cardtype.isNullOrEmpty()) {
+                resp.retcode = 1
+                resp.retmsg = "二维码解析后数据异常!"
+                return resp
+            }
+            val userSec = mobileApiService.findUserSecretByUid(uid)
+            val cityCard = cardService.findCardByCardnoAndCardtype(cardno, ConstantUtil.CARDTYPE_CITIZENCARD)
+            if(null==userSec || null == cityCard || cityCard.userid != userSec.userid){
+                resp.retcode = 1
+                resp.retmsg = "二维码用户信息错误,请刷新二维码后重试"
+                return resp
+            }
+            val person = userService.findPersonByUserid(cityCard.userid)
+            val bankcard = mobileApiService.findCardByUseridAndCardphyid(cityCard.userid, ConstantUtil.CARDTYPE_BANKCARD, cityCard.cardphyid)
+            if (null == person || null == bankcard) {
+                resp.retcode = 8
+                resp.retmsg = "用户市民卡信息异常"
+                return resp
+            }
+
+            resp.retcode = 0
+            resp.retmsg = "OK"
+            resp.userid = person.userid
+            resp.username = person.name
+            resp.sex = person.sex
+            resp.idtype = person.idtype
+            resp.idno = person.idno
+            resp.phone = person.mobile
+
+            resp.citycardno = cityCard.cardno
+            resp.cardphyid = cityCard.cardphyid
+            resp.expiredate = cityCard.expiredate
+            resp.cardstatus = cityCard.status
+            resp.transstatus = cityCard.transStatus
+            resp.bankcardno = bankcard.cardno
+            return resp
+
+        }else{
+            //老版本H5码,存redis
+            try {
+                val token = redisTemplate.opsForValue().get(qrcode) //fixme: 密文在redis中
+                if (token.isNullOrEmpty()) {
+                    resp.retcode = 1
+                    resp.retmsg = "二维码不存在或已失效"
+                    return resp
+                }
+
+                val encdataBack = AesUtil.decryptCFB(token, rootkey, iv, "AES/CFB/NoPadding")
+                if (encdataBack == null) {
+                    resp.retcode = 1
+                    resp.retmsg = "二维码解析失败"
+                    return resp
+                }
+                val uid = encdataBack.substring(0, encdataBack.indexOf(":"))
+//                val muser = mobileApiService.findUserById(uid)
+                val muser = mobileApiService.findUserSecretByUid(uid) //查找userSecret
+                if (muser == null) {
+                    resp.retcode = 1
+                    resp.retmsg = "用户不存在或状态异常"
+                    return resp
+                }
+                val seed32 = muser.secertkey
+                val rawEncdata = encdataBack.substring(encdataBack.indexOf(":") + 1)
+
+                val privateKey = RSAKeysGenerate.getPrivateKey(muser.rsaprivate)
+                val rawdataBack = RSAKeysGenerate.decryptbyte(privateKey, rawEncdata)
+                logger.info("rawdata_back=$rawdataBack")
+                val rawData = Gson().fromJson(rawdataBack, QrcodeRawData::class.java)
+                logger.info("userid=" + rawData.userid)
+                if (null == rawData || rawData.userid.isNullOrEmpty()) {
+                    resp.retcode = 1
+                    resp.retmsg = "用户不存在"
+                    return resp
+                }
+
+                val person = userService.findOnePersonByUserid(rawData.userid)
+                val bankcard = mobileApiService.findCardByUserid(person.userid)
+                if (null == bankcard) {
+                    resp.retcode = 9
+                    resp.retmsg = "二维码识别错误,手机绑定的银行卡已注销"
+                    return resp
+                }
+
+                //银行卡必定存在对应的市民卡
+                val citycard = mobileApiService.findCardByUseridAndCardphyid(person.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankcard.cardphyid)
+                if (null == citycard) {
+                    resp.retcode = 8
+                    resp.retmsg = "二维码识别异常"
+                    return resp
+                }
+
+                val verifyBarcode = QrCodeTotpUtil.verifyCode(rawData.totp, seed32, offset)
+                return if (verifyBarcode) {
+                    resp.retcode = 0
+                    resp.retmsg = "OK"
+                    resp.userid = person.userid
+                    resp.username = person.name
+                    resp.sex = person.sex
+                    resp.idtype = person.idtype
+                    resp.idno = person.idno
+                    resp.phone = person.mobile
+
+                    resp.citycardno = citycard.cardno
+                    resp.cardphyid = citycard.cardphyid
+                    resp.expiredate = citycard.expiredate
+                    resp.cardstatus = citycard.status
+                    resp.transstatus = citycard.transStatus
+                    resp.bankcardno = bankcard.cardno
+                    resp
+                } else {
+                    resp.retcode = 1
+                    resp.retmsg = "二维码校验失败"
+                    resp
+                }
+            } catch (e: Exception) {
+                e.printStackTrace()
+                resp.retcode = 1
+                resp.retmsg = "二维码识别异常"
+                return resp
+            }
+        }
+
+    }
+
+    override fun decodeCode(qrcode: String, chkTotp: Boolean, cardno: String?, tac: String?, amount: Int?, transdate: String?, transtime: String?): DoorQrcodeResponse {
+        val resp = DoorQrcodeResponse()
+        val config = qrcodeConfig()
+        val rootkey = config["rootkey"]
+        val iv = config["iv"]
+        val prefix = config["prefix"] ?: ""
+        val debug = "true" == config["debug"]
+        val offlineQRCode = "true" == config["offline"]
+        val totpoffset = config["offset"]
+        var offset = 3
+        if (NumberUtils.isDigits(totpoffset)) {
+            offset = totpoffset!!.toInt()
+        }
+        if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) {
+            resp.retcode = 1
+            resp.retmsg = "秘钥未配置"
+            return resp
+        }
+        if (!offlineQRCode) {
+            return decodeCode(qrcode) // fixme: 未启用脱机码,这里直接老版解码
+        }
+        println("citizencard QRcode=[" + qrcode + "],length=[" + qrcode.length + "]")
+
+        //解密
+        var data = HashMap<String, String>(0)
+        try {
+            val handle = QrCode.builder().rootKey(rootkey).iv(iv).prefix(prefix).debug(debug).create()
+            if (chkTotp) {
+                //联机解码需校验totp
+                data = handle.decodeWithTotpCheck(qrcode, offset) as HashMap<String, String>
+            } else {
+                //脱机码上传无需校验totp
+                data = handle.decodeWithoutTotpCheck(qrcode) as HashMap<String, String>
+                if (!data.isNullOrEmpty()) {
+                    if (!StringUtil.isEmpty(cardno) && cardno!!.trim() != data[QrCode.FIELD_CARDNO]) {
+                        resp.retcode = 1
+                        resp.retmsg = "市民卡号错误!"
+                        return resp //验证卡号是否一致
+                    }
+                    if (handle.tac(amount, transdate, transtime) != tac) {
+                        resp.retcode = 1
+                        resp.retmsg = "流水tac验证错误!"
+                        return resp //验证脱机流水tac是否一致
+                    }
+                }
+            }
+        } catch (ex: Exception) {
+            ex.printStackTrace()
+            resp.retcode = 1
+            resp.retmsg = "二维码解析报错,${ex.message}"
+            return resp
+        }
+
+        val uid = data[QrCode.FIELD_UID]
+        val cardno = data[QrCode.FIELD_CARDNO]
+        val cardtype = data[QrCode.FIELD_CARDTYPE] //公交卡类型
+        if (uid.isNullOrEmpty() || cardno.isNullOrEmpty() || cardtype.isNullOrEmpty()) {
+            resp.retcode = 1
+            resp.retmsg = "二维码解析后数据异常!"
+            return resp
+        }
+        val userSec = mobileApiService.findUserSecretByUid(uid)
+        val cityCard = cardService.findCardByCardnoAndCardtype(cardno, ConstantUtil.CARDTYPE_CITIZENCARD)
+        if (null == userSec || null == cityCard || cityCard.userid != userSec.userid) {
+            resp.retcode = 1
+            resp.retmsg = "二维码用户信息错误,请刷新二维码后重试"
+            return resp
+        }
+        val person = userService.findPersonByUserid(cityCard.userid)
+        val bankcard = mobileApiService.findCardByUseridAndCardphyid(cityCard.userid, ConstantUtil.CARDTYPE_BANKCARD, cityCard.cardphyid)
+        if (null == person || null == bankcard) {
+            resp.retcode = 8
+            resp.retmsg = "用户市民卡信息异常"
+            return resp
+        }
+
+        resp.retcode = 0
+        resp.retmsg = "OK"
+        resp.userid = person.userid
+        resp.username = person.name
+        resp.sex = person.sex
+        resp.idtype = person.idtype
+        resp.idno = person.idno
+        resp.phone = person.mobile
+
+        resp.citycardno = cityCard.cardno
+        resp.cardphyid = cityCard.cardphyid
+        resp.expiredate = cityCard.expiredate
+        resp.cardstatus = cityCard.status
+        resp.transstatus = cityCard.transStatus
+        resp.bankcardno = bankcard.cardno
+        return resp
+    }
+
+    override fun queryQRCodeKeys(timestamp: String): CitizenQrcodeKey {
+        val resp = CitizenQrcodeKey()
+        val offsetSec = DateUtil.getInterval(timestamp, DateUtil.getNow(DateUtil.DATETIME_FMT)) / 1000 //系统时间-请求时间 秒
+
+        val config = qrcodeConfig()
+        val rootkey = config["rootkey"]
+        val iv = config["iv"]
+        if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) {
+            resp.retcode = 1
+            resp.retmsg = "秘钥未配置"
+            return resp
+        }
+        resp.retcode = 0
+        resp.retmsg = "success"
+        resp.rootKey = rootkey
+        resp.iv = iv
+        resp.prefix = config["prefix"] ?: ""
+        resp.offsetSec = offsetSec
+        resp.totpStep = config["step"]?.toInt() ?: 5
+        resp.totpOffset = config["offset"]?.toInt() ?: 3
+        return resp
     }
 
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_monitor_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_monitor_service_impl.kt
new file mode 100644
index 0000000..b9d0031
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_monitor_service_impl.kt
@@ -0,0 +1,26 @@
+package com.supwisdom.dlpay.api.service.impl
+
+import com.google.gson.Gson
+import com.supwisdom.dlpay.agent.citizencard.DlpayResp
+import com.supwisdom.dlpay.api.service.TransactionMonitorService
+import com.supwisdom.dlpay.citizencard.dao.MonitorDlsmkCardpayDao
+import com.supwisdom.dlpay.citizencard.domain.TMonitorDlsmkCardpay
+import com.supwisdom.dlpay.framework.util.DateUtil
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+
+@Service
+class TransactionMonitorServiceImpl: TransactionMonitorService {
+    @Autowired
+    private lateinit var monitorDlsmkCardpayDao: MonitorDlsmkCardpayDao
+
+    override fun doMonitorCitizenCardPay(refno: String, milliSecond: Long, resp: DlpayResp) {
+        monitorDlsmkCardpayDao.save(TMonitorDlsmkCardpay().apply {
+            this.refno = refno
+            this.useMilliSec = milliSecond
+            this.respJson = Gson().toJson(resp)
+            this.requestUrl = "/api/consume/citizencard/payfinish"
+            this.createtime = DateUtil.getNow()
+        })
+    }
+}
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt
index d8b21ce..a9b2db2 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt
@@ -1,6 +1,7 @@
 package com.supwisdom.dlpay.api.service
 
 import com.supwisdom.dlpay.api.bean.ApiResponse
+import com.supwisdom.dlpay.api.bean.CitizenQrcodeKey
 import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse
 import com.supwisdom.dlpay.api.bean.QrcodeParam
 
@@ -8,4 +9,6 @@
     fun encodeCode(uid: String): ApiResponse
     fun encodeCode(param: QrcodeParam): ApiResponse
     fun decodeCode(qrcode: String): DoorQrcodeResponse
+    fun decodeCode(qrcode: String, chkTotp: Boolean, cardno: String?, tac: String?, amount:Int?, transdate:String?, transtime:String?): DoorQrcodeResponse
+    fun queryQRCodeKeys(timestamp: String): CitizenQrcodeKey
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_monitor_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_monitor_service.kt
new file mode 100644
index 0000000..1929347
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_monitor_service.kt
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.api.service
+
+import com.supwisdom.dlpay.agent.citizencard.DlpayResp
+import org.springframework.transaction.annotation.Transactional
+
+interface TransactionMonitorService {
+    @Transactional(rollbackFor = [Exception::class])
+    fun doMonitorCitizenCardPay(refno: String, milliSecond: Long, resp: DlpayResp)
+}
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
index 8de6f1e..1214614 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
@@ -3,6 +3,7 @@
 import com.supwisdom.dlpay.agent.citizencard.YnrccUtil
 import com.supwisdom.dlpay.agent.service.CitizencardPayService
 import com.supwisdom.dlpay.api.bean.JsonResult
+import com.supwisdom.dlpay.api.bean.SignBxyParam
 import com.supwisdom.dlpay.api.service.QRCodeService
 import com.supwisdom.dlpay.api.service.UserService
 import com.supwisdom.dlpay.api.util.MobileNumberCheck
@@ -427,14 +428,18 @@
         var needupdate = false
         if (resp.sinstatus == YnrccUtil.TRANSTYPE_SIGNCARD ) {
             signed = TradeDict.STATUS_YES
-            if(!card.signed){
-                card.signed = true
-                mobileApiService.saveCard(card)
-            }
-            user.signedtime = DateUtil.getNow();
+            //  如果银行返回已签约,保存密钥到UserSecret表
+            mobileApiService.signBxy(card, SignBxyParam().apply {
+                this.uid = user.uid
+                this.userid = user.userid
+                this.secertkey = user.secertkey
+                this.rsapublic = user.rsapublic
+                this.rsaprivate = user.rsaprivate
+            })
+
+            user.signedtime = DateUtil.getNow()
             mobileApiService.saveUser(user)
             needupdate = true;
-
         }
         if( user.userid.isNullOrEmpty()){
             user.userid = person.userid
@@ -480,12 +485,17 @@
 
         if (resp.sinstatus == YnrccUtil.TRANSTYPE_SIGNCARD ) {
             signed = TradeDict.STATUS_YES
-            if(!card.signed){
-                card.signed = true
-                mobileApiService.saveCard(card)
-            }
-            user.signedtime = DateUtil.getNow();
+            //  如果银行返回已签约,保存密钥到UserSecret表
+            mobileApiService.signBxy(card, SignBxyParam().apply {
+                this.uid=user.uid
+                this.userid = user.userid
+                this.secertkey = user.secertkey
+                this.rsapublic = user.rsapublic
+                this.rsaprivate = user.rsaprivate
+            })
+            user.signedtime = DateUtil.getNow()
             mobileApiService.saveUser(user)
+
         }
 
         return JsonResult.ok("OK")
@@ -611,10 +621,15 @@
             if (resp.code != "0000") {
                 return JsonResult.error(resp.message)
             }
-            card.signed = true
             user.signedtime = DateUtil.getNow()
-            mobileApiService.saveCard(card)
             mobileApiService.saveUser(user)
+            mobileApiService.signBxy(card, SignBxyParam().apply {
+                this.uid=user.uid
+                this.userid = user.userid
+                this.secertkey = user.secertkey
+                this.rsapublic = user.rsapublic
+                this.rsaprivate = user.rsaprivate
+            })
             signed = TradeDict.STATUS_YES
         } else {
             return JsonResult.error("请先绑定银行卡")
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
index 55d717c..3d029aa 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
@@ -1,7 +1,10 @@
 package com.supwisdom.dlpay.mobile.service
 
 import com.supwisdom.dlpay.api.bean.BaseResp
+import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse
+import com.supwisdom.dlpay.api.bean.SignBxyParam
 import com.supwisdom.dlpay.api.domain.TCard
+import com.supwisdom.dlpay.api.domain.TUserSecret
 import com.supwisdom.dlpay.mobile.domain.TBMobileUser
 import com.supwisdom.dlpay.mobile.domain.TBPages
 
@@ -22,9 +25,15 @@
 
     fun saveCard(card:TCard):TCard
 
+    fun signBxy(card:TCard,param: SignBxyParam)
+
     fun sendSms(phone:String,code:String):BaseResp
 
     fun findByUseridAndStatus(userid:String,status:String):List<TBMobileUser>?
 
     fun findCardByUseridAndCardphyid(userid: String, cardtype: String, cardphyid: String?): TCard?
+
+    fun findUserSecretByUid(uid:String):TUserSecret?
+
+    fun findCardInfoByMobileUid(uid: String): DoorQrcodeResponse
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
index f9c2b1e..de74d84 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
@@ -2,8 +2,13 @@
 
 import com.mascloud.sdkclient.Client
 import com.supwisdom.dlpay.api.bean.BaseResp
+import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse
+import com.supwisdom.dlpay.api.bean.SignBxyParam
 import com.supwisdom.dlpay.api.dao.CardDao
+import com.supwisdom.dlpay.api.dao.PersonDao
+import com.supwisdom.dlpay.api.dao.UserSecretDao
 import com.supwisdom.dlpay.api.domain.TCard
+import com.supwisdom.dlpay.api.domain.TUserSecret
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.mobile.dao.MobileUserDao
 import com.supwisdom.dlpay.mobile.dao.PagesDao
@@ -26,9 +31,15 @@
     lateinit var cardDao: CardDao
 
     @Autowired
+    lateinit var personDao: PersonDao
+
+    @Autowired
     lateinit var pagesDao: PagesDao
 
     @Autowired
+    lateinit var userSecretDao: UserSecretDao
+
+    @Autowired
     lateinit var systemUtilService: SystemUtilService
     companion object {
         var isMsgLogined: Boolean = false
@@ -71,6 +82,28 @@
         return cardDao.save(card)
     }
 
+    override fun signBxy(card: TCard, param: SignBxyParam) {
+        card.signed = true
+        cardDao.save(card)
+        val optional = userSecretDao.findById(param.uid)
+        if (optional.isPresent) {
+            val userSecret = optional.get()
+            userSecret.userid = param.userid
+            userSecret.rsaprivate = param.rsaprivate
+            userSecret.rsapublic = param.rsapublic
+            userSecret.secertkey = param.secertkey
+            userSecretDao.save(userSecret)
+        } else {
+            val userSecret = TUserSecret()
+            userSecret.uid = param.uid
+            userSecret.userid = param.userid
+            userSecret.rsaprivate = param.rsaprivate
+            userSecret.rsapublic = param.rsapublic
+            userSecret.secertkey = param.secertkey
+            userSecretDao.save(userSecret)
+        }
+    }
+
     override fun sendSms(phone: String, code: String): BaseResp {
         var resp = BaseResp()
         var url = systemUtilService.getBusinessValue("sms.url")
@@ -181,4 +214,53 @@
             cardDao.findBankcardByCitizencard(userid, cardtype, cardphyid)
         }
     }
+
+    override fun findUserSecretByUid(uid: String): TUserSecret? {
+        return userSecretDao.getByUid(uid)
+    }
+
+    override fun findCardInfoByMobileUid(uid: String): DoorQrcodeResponse {
+        val resp = DoorQrcodeResponse()
+        val mobileUser = userSecretDao.getByUid(uid)
+        if (null == mobileUser) {
+            resp.retcode = 1
+            resp.retmsg = "用户不存在!"
+            return resp
+        }
+        val person = personDao.findByUserid(mobileUser.userid)
+        if (null == person) {
+            resp.retcode = 1
+            resp.retmsg = "用户不存在!"
+            return resp
+        }
+        val bankCard = cardDao.findCardByUseridAndCardtype(person.userid, ConstantUtil.CARDTYPE_BANKCARD)
+        if (null == bankCard) {
+            resp.retcode = 1
+            resp.retmsg = "用户未绑定银行卡!"
+            return resp
+        }
+        val cityCard = cardDao.findBankcardByCitizencard(bankCard.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankCard.cardphyid)
+        if (null == cityCard) {
+            resp.retcode = 1
+            resp.retmsg = "用户市民卡信息错误!"
+            return resp
+        }
+
+        resp.retcode = 0
+        resp.retmsg = "OK"
+        resp.userid = person.userid
+        resp.username = person.name
+        resp.sex = person.sex
+        resp.idtype = person.idtype
+        resp.idno = person.idno
+        resp.phone = person.mobile
+
+        resp.citycardno = cityCard.cardno
+        resp.cardphyid = cityCard.cardphyid
+        resp.expiredate = cityCard.expiredate
+        resp.cardstatus = cityCard.status
+        resp.transstatus = cityCard.transStatus
+        resp.bankcardno = bankCard.cardno
+        return resp
+    }
 }
\ No newline at end of file