公交二维码对接接口
diff --git a/build.gradle b/build.gradle
index 4cbb0ed..1d0b788 100644
--- a/build.gradle
+++ b/build.gradle
@@ -56,7 +56,7 @@
     compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.10.1'
     compile group: 'org.apache.poi', name: 'poi-ooxml-schemas', version: '3.10.1'
     compile group: 'org.apache.poi', name: 'poi-scratchpad', version: '3.10.1'
-    compile 'com.supwisdom:payapi-sdk:1.0.11'
+    compile 'com.supwisdom:payapi-sdk:1.0.28-2-g03919bd.dirty'
     compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: '2.3.4.RELEASE'
 
 
diff --git a/config/application-devel-pg.properties b/config/application-devel-pg.properties
index 9342bff..2f72996 100644
--- a/config/application-devel-pg.properties
+++ b/config/application-devel-pg.properties
@@ -4,8 +4,8 @@
 spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
 spring.datasource.continue-on-error=true
 #spring.datasource.initialization-mode=always
-spring.jpa.show-sql=true
-logging.level.org.hibernate.SQL=DEBUG
+spring.jpa.show-sql=false
+logging.level.org.hibernate.SQL=ERROR
 # Postgresql settings
 spring.datasource.platform=postgresql
 spring.datasource.url=jdbc:postgresql://172.28.201.70:15432/restauranttest
@@ -25,12 +25,21 @@
 spring.jackson.serialization.fail-on-empty-beans=false
 
 
-payapi.url=http://172.28.201.70:10010/payapi
+#payapi.url=http://172.28.201.70:10010/payapi
+payapi.url=http://localhost:8080/payapi
 
 payapi.appid=200001
+payapi.logintime= 0 0/5 * * * ? 
 
 cron.offlinedtl= 0 0/5 * * * ?
-payapi.logintime= 0 0/5 * * * ? 
-busapp.cardsync.cron=0 0/1 * * * ?
+#busapp.cardsync.cron=0 0/1 * * * ?
+busapp.cardsync.cron=-
 payapi.submitOfflineDtl=-
-payapi.checkWipDtl=-
\ No newline at end of file
+payapi.checkWipDtl=-
+busapp.upload.transdtl.task.cron=0 0/1 * * * ?
+
+#restaurant.sync.card.task.cron=0 0/1 * * * ?
+restaurant.sync.card.task.cron=-
+restaurant.chkdtltask.cron=-
+
+server.servlet.context-path=/carbus
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/BusApiResp.java b/src/main/java/com/supwisdom/dlpay/bus/bean/BusApiResp.java
index 4956990..82f3d09 100644
--- a/src/main/java/com/supwisdom/dlpay/bus/bean/BusApiResp.java
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/BusApiResp.java
@@ -4,6 +4,14 @@
   private String retcode;
   private String retmsg;
 
+  public BusApiResp() {
+  }
+
+  public BusApiResp(String retcode, String retmsg) {
+    this.retcode = retcode;
+    this.retmsg = retmsg;
+  }
+
   public String getRetcode() {
     return retcode;
   }
@@ -20,6 +28,15 @@
     this.retmsg = retmsg;
   }
 
+  public void error(String retcode, String retmsg) {
+    this.retcode = retcode;
+    this.retmsg = retmsg;
+  }
+
+  public String errmsg() {
+    return "retcode=[" + retcode + "],retmsg=[" + retmsg + "]";
+  }
+
   @Override
   public String toString() {
     return "retcode=[" + retcode + "],retmsg=[" + retmsg + "]";
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeBaseReq.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeBaseReq.java
new file mode 100644
index 0000000..b9ff163
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeBaseReq.java
@@ -0,0 +1,138 @@
+package com.supwisdom.dlpay.bus.bean;
+
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+import com.supwisdom.dlpay.framework.util.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.beans.Introspector;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class QrcodeBaseReq {
+  @Sign
+  private String partner_id;
+  @Sign
+  private String timestamp;
+  @Sign
+  private String sign;
+  @Sign
+  private String sign_method;
+  @Sign
+  private String version;
+
+  private static final Logger logger = LoggerFactory.getLogger(APIRequestParam.class);
+
+  public String getPartner_id() {
+    return partner_id;
+  }
+
+  public void setPartner_id(String partner_id) {
+    this.partner_id = partner_id;
+  }
+
+  public String getTimestamp() {
+    return timestamp;
+  }
+
+  public void setTimestamp(String timestamp) {
+    this.timestamp = timestamp;
+  }
+
+  public String getSign() {
+    return sign;
+  }
+
+  public void setSign(String sign) {
+    this.sign = sign;
+  }
+
+  public String getSign_method() {
+    return sign_method;
+  }
+
+  public void setSign_method(String sign_method) {
+    this.sign_method = sign_method;
+  }
+
+  public String getVersion() {
+    return version;
+  }
+
+  public void setVersion(String version) {
+    this.version = version;
+  }
+
+  private boolean calcSignAndCheck(Map<String, String> map, String key) {
+    String sign = map.get("sign");
+    String signType = map.get("sign_method");
+    if (StringUtil.isEmpty(sign)) return false;
+
+    String signdata = StringUtil.createLinkString(StringUtil.swParaFilter(map)); //除了sign所有的非空(null或"")都要参与签名
+    logger.info("signdata=[" + signdata + "]");
+
+    String calcSign = null;
+    if ("HMAC".equalsIgnoreCase(signType)) {
+      calcSign = HmacUtil.HMACSHA1(signdata, key);
+    } else if ("MD5".equalsIgnoreCase(signType)) {
+      calcSign = MD5.encodeByMD5(signdata + key);
+    }
+
+    if (sign.equalsIgnoreCase(calcSign)) {
+      return true;
+    }
+    return false;
+  }
+
+  public boolean checkSign(String key) {
+    Class clazz = this.getClass();
+    Map<String, String> paramMap = new HashMap<>();
+    Method[] allGetter = clazz.getMethods();
+
+    for (Method meth : allGetter) {
+      if (meth.getName().startsWith("get") || meth.getName().startsWith("is")) {
+        String fieldName = Introspector.decapitalize(meth.getName().substring(meth.getName().startsWith("get") ? 3 : 2));
+        Field field;
+        try {
+          field = clazz.getDeclaredField(fieldName);
+        } catch (NoSuchFieldException e) {
+          try {
+            field = clazz.getSuperclass().getDeclaredField(fieldName);
+          } catch (NoSuchFieldException e1) {
+            continue;
+          }
+        }
+
+        if (field.isAnnotationPresent(Sign.class)) {
+          Object value;
+          try {
+            value = meth.invoke(this);
+          } catch (Exception e) {
+            e.printStackTrace();
+            continue;
+          }
+          paramMap.put(fieldName, value == null ? null : value.toString());
+        }
+      }
+    }
+    return calcSignAndCheck(paramMap, key);
+  }
+
+  public abstract boolean checkParam() throws ReqErrorException;
+
+  public boolean baseCheck() throws ReqErrorException {
+    if (StringUtil.isEmpty(this.partner_id)) throw new ReqErrorException("请求参数错误[合作伙伴ID为空]");
+    if (!DateUtil.checkDatetimeValid(timestamp, "yyyyMMddHHmmss")) throw new ReqErrorException("请求参数错误[时间戳格式错误]");
+    final String nowTime = DateUtil.getNow("yyyyMMddHHmmss");
+    if (DateUtil.compareDatetime(nowTime, this.timestamp, -600) > 0 || DateUtil.compareDatetime(nowTime, this.timestamp, 600) < 0)
+      throw new ReqErrorException("请求参数错误[时间戳误差10分钟以上]");
+
+    if (StringUtil.isEmpty(sign)) throw new ReqErrorException("请求参数错误[签名为空]");
+    if (!Arrays.asList("HMAC", "MD5").contains(sign_method)) throw new ReqErrorException("请求参数错误[签名方法错误]");
+    if (StringUtil.isEmpty(this.version)) throw new ReqErrorException("请求参数错误[版本号为空]");
+    return true;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeKeysReq.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeKeysReq.java
new file mode 100644
index 0000000..fc20a25
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeKeysReq.java
@@ -0,0 +1,10 @@
+package com.supwisdom.dlpay.bus.bean;
+
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+
+public class QrcodeKeysReq extends QrcodeBaseReq {
+  @Override
+  public boolean checkParam() throws ReqErrorException {
+    return super.baseCheck();
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeKeysResp.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeKeysResp.java
new file mode 100644
index 0000000..d64b0b5
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeKeysResp.java
@@ -0,0 +1,13 @@
+package com.supwisdom.dlpay.bus.bean;
+
+public class QrcodeKeysResp extends BusApiResp {
+  private String key;
+
+  public String getKey() {
+    return key;
+  }
+
+  public void setKey(String key) {
+    this.key = key;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayCancelReq.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayCancelReq.java
new file mode 100644
index 0000000..14d6f99
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayCancelReq.java
@@ -0,0 +1,46 @@
+package com.supwisdom.dlpay.bus.bean;
+
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+import com.supwisdom.dlpay.framework.util.Sign;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+
+public class QrcodePayCancelReq extends QrcodeBaseReq {
+  @Sign
+  private String billno;
+  @Sign
+  private String refno;
+  @Sign
+  private String cancelBillno;
+
+  public String getBillno() {
+    return billno;
+  }
+
+  public void setBillno(String billno) {
+    this.billno = billno;
+  }
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  public String getCancelBillno() {
+    return cancelBillno;
+  }
+
+  public void setCancelBillno(String cancelBillno) {
+    this.cancelBillno = cancelBillno;
+  }
+
+  @Override
+  public boolean checkParam() throws ReqErrorException {
+    super.baseCheck();
+    if (StringUtil.isEmpty(refno) && StringUtil.isEmpty(billno)) throw new ReqErrorException("请求参数错误[原始订单号不能都为空]");
+    if (StringUtil.isEmpty(cancelBillno)) throw new ReqErrorException("请求参数错误[撤销的商户订单号不能为空]");
+    return true;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayCancelResp.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayCancelResp.java
new file mode 100644
index 0000000..5d5eb78
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayCancelResp.java
@@ -0,0 +1,13 @@
+package com.supwisdom.dlpay.bus.bean;
+
+public class QrcodePayCancelResp extends BusApiResp {
+  private String refno;
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayConfirmReq.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayConfirmReq.java
new file mode 100644
index 0000000..9561ddf
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayConfirmReq.java
@@ -0,0 +1,35 @@
+package com.supwisdom.dlpay.bus.bean;
+
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+import com.supwisdom.dlpay.framework.util.Sign;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+
+public class QrcodePayConfirmReq extends QrcodeBaseReq {
+  @Sign
+  private String billno;
+  @Sign
+  private String refno;
+
+  public String getBillno() {
+    return billno;
+  }
+
+  public void setBillno(String billno) {
+    this.billno = billno;
+  }
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  @Override
+  public boolean checkParam() throws ReqErrorException {
+    super.baseCheck();
+    if (StringUtil.isEmpty(refno) && StringUtil.isEmpty(billno)) throw new ReqErrorException("请求参数错误[流水号不能都为空]");
+    return true;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayInitReq.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayInitReq.java
new file mode 100644
index 0000000..3b6e8c2
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayInitReq.java
@@ -0,0 +1,101 @@
+package com.supwisdom.dlpay.bus.bean;
+
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+import com.supwisdom.dlpay.framework.util.DateUtil;
+import com.supwisdom.dlpay.framework.util.Sign;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+
+public class QrcodePayInitReq extends QrcodeBaseReq {
+  @Sign
+  private String qrcode;  //二维码
+  @Sign
+  private String billno;  //商户订单号
+  @Sign
+  private Long amount; //交易金额
+  @Sign
+  private String transDate; //交易日期 yyyyMMdd
+  @Sign
+  private String transTime; //交易时间
+  @Sign
+  private String termdesc; //终端描述,可空
+  @Sign
+  private String cardNo;  //市民卡号,可空
+  @Sign
+  private String tac; //二维码计算的流水tac,可空
+
+  @Override
+  public boolean checkParam() throws ReqErrorException {
+    super.baseCheck();
+    if (StringUtil.isEmpty(this.qrcode)) throw new ReqErrorException("请求参数错误[二维码为空]");
+    if (StringUtil.isEmpty(this.billno)) throw new ReqErrorException("请求参数错误[商户订单号为空]");
+    if (null == this.amount || this.amount <= 0) throw new ReqErrorException("请求参数错误[交易金额必须大于0]");
+    if (!DateUtil.checkDatetimeValid(this.transDate, "yyyyMMdd")) throw new ReqErrorException("请求参数错误[交易日期格式错误]");
+    if (!DateUtil.checkDatetimeValid(this.transDate + this.transTime, "yyyyMMddHHmmss"))
+      throw new ReqErrorException("请求参数错误[交易时间格式错误]");
+    return true;
+  }
+
+  public String getQrcode() {
+    return qrcode;
+  }
+
+  public void setQrcode(String qrcode) {
+    this.qrcode = qrcode;
+  }
+
+  public String getCardNo() {
+    return cardNo;
+  }
+
+  public void setCardNo(String cardNo) {
+    this.cardNo = cardNo;
+  }
+
+  public String getBillno() {
+    return billno;
+  }
+
+  public void setBillno(String billno) {
+    this.billno = billno;
+  }
+
+  public Long getAmount() {
+    return amount;
+  }
+
+  public void setAmount(Long amount) {
+    this.amount = amount;
+  }
+
+  public String getTransDate() {
+    return transDate;
+  }
+
+  public void setTransDate(String transDate) {
+    this.transDate = transDate;
+  }
+
+  public String getTransTime() {
+    return transTime;
+  }
+
+  public void setTransTime(String transTime) {
+    this.transTime = transTime;
+  }
+
+  public String getTac() {
+    return tac;
+  }
+
+  public void setTac(String tac) {
+    this.tac = tac;
+  }
+
+  public String getTermdesc() {
+    return termdesc;
+  }
+
+  public void setTermdesc(String termdesc) {
+    this.termdesc = termdesc;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayResp.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayResp.java
new file mode 100644
index 0000000..0dee18a
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodePayResp.java
@@ -0,0 +1,31 @@
+package com.supwisdom.dlpay.bus.bean;
+
+public class QrcodePayResp extends BusApiResp {
+  private String refno;
+  private String billno;
+  private Integer amount;
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  public String getBillno() {
+    return billno;
+  }
+
+  public void setBillno(String billno) {
+    this.billno = billno;
+  }
+
+  public Integer getAmount() {
+    return amount;
+  }
+
+  public void setAmount(Integer amount) {
+    this.amount = amount;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryReq.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryReq.java
new file mode 100644
index 0000000..d3deca6
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryReq.java
@@ -0,0 +1,36 @@
+package com.supwisdom.dlpay.bus.bean;
+
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+import com.supwisdom.dlpay.framework.util.Sign;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+
+public class QrcodeQueryReq extends QrcodeBaseReq  {
+  @Sign
+  private String uid;
+  @Sign
+  private String userid;
+
+  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;
+  }
+
+  @Override
+  public boolean checkParam() throws ReqErrorException {
+    super.baseCheck();
+    if(StringUtil.isEmpty(uid)) throw new ReqErrorException("请求参数错误[手机唯一号不能为空]");
+    if(StringUtil.isEmpty(userid)) throw new ReqErrorException("请求参数错误[用户唯一号不能为空]");
+    return true;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResp.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResp.java
new file mode 100644
index 0000000..9309f5e
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResp.java
@@ -0,0 +1,13 @@
+package com.supwisdom.dlpay.bus.bean;
+
+public class QrcodeQueryResp extends BusApiResp{
+  private String qrcode;
+
+  public String getQrcode() {
+    return qrcode;
+  }
+
+  public void setQrcode(String qrcode) {
+    this.qrcode = qrcode;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResultReq.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResultReq.java
new file mode 100644
index 0000000..756390d
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResultReq.java
@@ -0,0 +1,35 @@
+package com.supwisdom.dlpay.bus.bean;
+
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+import com.supwisdom.dlpay.framework.util.Sign;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+
+public class QrcodeQueryResultReq extends QrcodeBaseReq {
+  @Sign
+  private String billno;
+  @Sign
+  private String refno;
+
+  public String getBillno() {
+    return billno;
+  }
+
+  public void setBillno(String billno) {
+    this.billno = billno;
+  }
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  @Override
+  public boolean checkParam() throws ReqErrorException {
+    super.baseCheck();
+    if (StringUtil.isEmpty(refno) && StringUtil.isEmpty(billno)) throw new ReqErrorException("请求参数错误[流水号不能都为空]");
+    return true;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResultResp.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResultResp.java
new file mode 100644
index 0000000..e63e969
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeQueryResultResp.java
@@ -0,0 +1,94 @@
+package com.supwisdom.dlpay.bus.bean;
+
+public class QrcodeQueryResultResp extends BusApiResp {
+  private String billno;
+  private String refno;
+  private Integer amount;
+  private String status;
+  private String cardno;
+  private String sourceType;
+  private String reverseFlag;
+  private String transdesc;
+  private String termdesc;
+  private String remark;
+
+  public String getBillno() {
+    return billno;
+  }
+
+  public void setBillno(String billno) {
+    this.billno = billno;
+  }
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  public Integer getAmount() {
+    return amount;
+  }
+
+  public void setAmount(Integer amount) {
+    this.amount = amount;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  public String getCardno() {
+    return cardno;
+  }
+
+  public void setCardno(String cardno) {
+    this.cardno = cardno;
+  }
+
+  public String getSourceType() {
+    return sourceType;
+  }
+
+  public void setSourceType(String sourceType) {
+    this.sourceType = sourceType;
+  }
+
+  public String getReverseFlag() {
+    return reverseFlag;
+  }
+
+  public void setReverseFlag(String reverseFlag) {
+    this.reverseFlag = reverseFlag;
+  }
+
+  public String getTransdesc() {
+    return transdesc;
+  }
+
+  public void setTransdesc(String transdesc) {
+    this.transdesc = transdesc;
+  }
+
+  public String getTermdesc() {
+    return termdesc;
+  }
+
+  public void setTermdesc(String termdesc) {
+    this.termdesc = termdesc;
+  }
+
+  public String getRemark() {
+    return remark;
+  }
+
+  public void setRemark(String remark) {
+    this.remark = remark;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeStatementReq.java b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeStatementReq.java
new file mode 100644
index 0000000..5710713
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/bean/QrcodeStatementReq.java
@@ -0,0 +1,25 @@
+package com.supwisdom.dlpay.bus.bean;
+
+import com.supwisdom.dlpay.api.util.DateUtil;
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+import com.supwisdom.dlpay.framework.util.Sign;
+
+public class QrcodeStatementReq extends QrcodeBaseReq  {
+  @Sign
+  private String checkDate;
+
+  public String getCheckDate() {
+    return checkDate;
+  }
+
+  public void setCheckDate(String checkDate) {
+    this.checkDate = checkDate;
+  }
+
+  @Override
+  public boolean checkParam() throws ReqErrorException {
+    super.baseCheck();
+    if (!DateUtil.checkDatetimeValid(checkDate, DateUtil.DATE_FMT))  throw new ReqErrorException("对账日期错误[yyyyMMdd]");
+    return true;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/controller/BusQrcodeConsumeController.java b/src/main/java/com/supwisdom/dlpay/bus/controller/BusQrcodeConsumeController.java
new file mode 100644
index 0000000..5aa017a
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/controller/BusQrcodeConsumeController.java
@@ -0,0 +1,378 @@
+package com.supwisdom.dlpay.bus.controller;
+
+import com.google.gson.Gson;
+import com.supwisdom.dlpay.bus.bean.*;
+import com.supwisdom.dlpay.bus.domain.TThirdpartQrcodeTransdtl;
+import com.supwisdom.dlpay.bus.service.QrcodeConsumeService;
+import com.supwisdom.dlpay.bus.util.BusConstant;
+import com.supwisdom.dlpay.bus.util.BusException;
+import com.supwisdom.dlpay.bus.util.ReqErrorException;
+import com.supwisdom.dlpay.bus.util.RespCode;
+import com.supwisdom.dlpay.framework.domain.TApiClient;
+import com.supwisdom.dlpay.framework.service.SystemUtilService;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 珠海益达乘车码消费接口
+ * 提供给第三方二维码相关接口
+ * 提供appId和appKey即可,无需获取jwt
+ */
+@RestController
+@RequestMapping("/api/thirdpay")
+public class BusQrcodeConsumeController {
+  @Autowired
+  private QrcodeConsumeService qrcodeConsumeService;
+  @Autowired
+  private SystemUtilService systemUtilService;
+
+  private static final Gson _gson = new Gson();
+  private static final Logger logger = LoggerFactory.getLogger(BusQrcodeConsumeController.class);
+
+  private void print(String url, BusApiResp resp) {
+    logger.error("url=[" + url + "]返回错误:" + resp.errmsg());
+  }
+
+  /**
+   * 获取二维码解码密钥
+   */
+  @PostMapping("/qrcode/keys")
+  public QrcodeKeysResp queryConfig(@ModelAttribute QrcodeKeysReq req) {
+    final String url = "/api/thirdpay/qrcode/keys";
+    QrcodeKeysResp resp = new QrcodeKeysResp();
+    try {
+      req.checkParam();
+      TApiClient apiClient = qrcodeConsumeService.getApiClientByAppId(req.getPartner_id());
+      if (null == apiClient) {
+        throw new ReqErrorException("请求参数错误[合作伙伴ID不存在]");
+      }
+
+      if (!req.checkSign(apiClient.getSecret())) {
+        resp.error(RespCode.SIGN_CHECK_ERROR, "签名错误");
+        print(url, resp);
+        return resp;
+      }
+
+      String key = qrcodeConsumeService.getPayapiQRCodeRootKey(apiClient.getSecret(), req.getTimestamp());
+      resp.setRetcode(RespCode.SUCCESS);
+      resp.setRetmsg("success");
+      resp.setKey(key);
+      logger.info("url=[" + url + "]成功返回:" + _gson.toJson(resp));
+      return resp;
+    } catch (ReqErrorException rex) {
+      resp.error(RespCode.REQ_PARAM_ERROR, rex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (BusException bex) {
+      resp.error(RespCode.BUSINESS_PROCESS_ERROR, bex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      resp.error(RespCode.OTHER_ERROR, "system error!");
+      print(url, resp);
+      return resp;
+    }
+  }
+
+  /**
+   * 获取二维码
+   * */
+  @PostMapping("/qrcode")
+  public QrcodeQueryResp queryQrcode(@ModelAttribute QrcodeQueryReq req) {
+    final String url = "/api/thirdpay/qrcode";
+    QrcodeQueryResp resp = new QrcodeQueryResp();
+    try {
+      req.checkParam();
+      TApiClient apiClient = qrcodeConsumeService.getApiClientByAppId(req.getPartner_id());
+      if (null == apiClient) {
+        throw new ReqErrorException("请求参数错误[合作伙伴ID不存在]");
+      }
+
+      if (!req.checkSign(apiClient.getSecret())) {
+        resp.error(RespCode.SIGN_CHECK_ERROR, "签名错误");
+        print(url, resp);
+        return resp;
+      }
+
+      String data = qrcodeConsumeService.doQueryQRcode(req.getUid().trim(), req.getUserid().trim());
+      if(StringUtil.isEmpty(data)){
+        resp.error(RespCode.BUSINESS_PROCESS_ERROR, "获取二维码失败!");
+        print(url, resp);
+        return resp;
+      }
+
+      resp.setRetcode(RespCode.SUCCESS);
+      resp.setRetmsg("success");
+      resp.setQrcode(data);
+      logger.info("url=[" + url + "]成功返回:" + _gson.toJson(resp));
+      return resp;
+    } catch (ReqErrorException rex) {
+      resp.error(RespCode.REQ_PARAM_ERROR, rex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (BusException bex) {
+      resp.error(RespCode.BUSINESS_PROCESS_ERROR, bex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      resp.error(RespCode.OTHER_ERROR, "system error!");
+      print(url, resp);
+      return resp;
+    }
+  }
+
+  /**
+   * 联机
+   * 二维码联机消费初始化,解码后会校验totp
+   */
+  @PostMapping("/qrcode/online/payInit")
+  public QrcodePayResp onlinePayInit(@ModelAttribute QrcodePayInitReq req) {
+    return payInit(false, req);
+  }
+
+  /**
+   * 脱机(脱机流水上传)
+   * 二维码脱机消费初始化,解码后不校验totp
+   */
+  @PostMapping("/qrcode/offline/payInit")
+  public QrcodePayResp offlinePayInit(@ModelAttribute QrcodePayInitReq req) {
+    return payInit(true, req);
+  }
+
+  private QrcodePayResp payInit(boolean offlineFlag, QrcodePayInitReq req) {
+    String url = offlineFlag ? "/api/thirdpay/qrcode/offline/payInit" : "/api/thirdpay/qrcode/online/payInit";
+    QrcodePayResp resp = new QrcodePayResp();
+    try {
+      req.checkParam();
+      if(offlineFlag){
+        //脱机二维码,要求设备解码后传cardNo和流水tac
+        if (StringUtil.isEmpty(req.getCardNo())) throw new ReqErrorException("请求参数错误[市民卡号为空]");
+        if (StringUtil.isEmpty(req.getTac())) throw new ReqErrorException("请求参数错误[流水TAC为空]");
+      }
+
+      TApiClient apiClient = qrcodeConsumeService.getApiClientByAppId(req.getPartner_id());
+      if (null == apiClient) {
+        throw new ReqErrorException("请求参数错误[合作伙伴ID不存在]");
+      }
+
+      if (!req.checkSign(apiClient.getSecret())) {
+        resp.error(RespCode.SIGN_CHECK_ERROR, "签名错误");
+        print(url, resp);
+        return resp;
+      }
+
+      String shopaccno = systemUtilService.getBusinessValue(BusConstant.QRCODE_THIRDPAY_SHOP_PREFIX + apiClient.getAppid().trim());
+      if (StringUtil.isEmpty(shopaccno)) {
+        resp.error(RespCode.SHOP_NOT_CONFIG, "收费商户未配置");
+        print(url, resp);
+        return resp;
+      }
+
+      TThirdpartQrcodeTransdtl transdtl = qrcodeConsumeService.saveQRcodeTransdtl(req.getPartner_id().trim(), req.getBillno().trim(),
+          req.getCardNo(), req.getAmount(), req.getTransDate(), req.getTransTime(), req.getQrcode(), req.getTac(), req.getTermdesc(), shopaccno.trim(), offlineFlag);
+
+      boolean ret = qrcodeConsumeService.doSendQRcodePayInit(transdtl, resp);
+      if (ret) {
+        logger.info("url=[" + url + "]成功返回:" + _gson.toJson(resp));
+      } else {
+        print(url, resp);
+      }
+      return resp;
+    } catch (ReqErrorException rex) {
+      resp.error(RespCode.REQ_PARAM_ERROR, rex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (BusException bex) {
+      resp.error(RespCode.BUSINESS_PROCESS_ERROR, bex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      resp.error(RespCode.OTHER_ERROR, "system error!");
+      print(url, resp);
+      return resp;
+    }
+  }
+
+  /**
+   * 消费确认
+   */
+  @PostMapping("/payConfirm")
+  public QrcodePayResp payConfirm(@ModelAttribute QrcodePayConfirmReq req) {
+    String url = "/api/thirdpay/payConfirm";
+    QrcodePayResp resp = new QrcodePayResp();
+    try {
+      req.checkParam();
+      TApiClient apiClient = qrcodeConsumeService.getApiClientByAppId(req.getPartner_id());
+      if (null == apiClient) {
+        throw new ReqErrorException("请求参数错误[合作伙伴ID不存在]");
+      }
+
+      if (!req.checkSign(apiClient.getSecret())) {
+        resp.error(RespCode.SIGN_CHECK_ERROR, "签名错误");
+        print(url, resp);
+        return resp;
+      }
+
+      boolean ret = qrcodeConsumeService.doConfirmQRcodePay(req.getPartner_id().trim(), req.getRefno(), req.getBillno(), resp);
+      if (ret) {
+        logger.info("url=[" + url + "]成功返回:" + _gson.toJson(resp));
+      } else {
+        print(url, resp);
+      }
+      return resp;
+    } catch (ReqErrorException rex) {
+      resp.error(RespCode.REQ_PARAM_ERROR, rex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (BusException bex) {
+      resp.error(RespCode.BUSINESS_PROCESS_ERROR, bex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      resp.error(RespCode.OTHER_ERROR, "system error!");
+      print(url, resp);
+      return resp;
+    }
+  }
+
+  /**
+   * 消费撤销
+   */
+  @PostMapping("/payCancel")
+  public QrcodePayCancelResp payCancel(@ModelAttribute QrcodePayCancelReq req) {
+    String url = "/api/thirdpay/payCancel";
+    QrcodePayCancelResp resp = new QrcodePayCancelResp();
+    try {
+      req.checkParam();
+      TApiClient apiClient = qrcodeConsumeService.getApiClientByAppId(req.getPartner_id());
+      if (null == apiClient) {
+        throw new ReqErrorException("请求参数错误[合作伙伴ID不存在]");
+      }
+
+      if (!req.checkSign(apiClient.getSecret())) {
+        resp.error(RespCode.SIGN_CHECK_ERROR, "签名错误");
+        print(url, resp);
+        return resp;
+      }
+
+      boolean ret = qrcodeConsumeService.doCancelQRcodeTransdtl(req.getPartner_id().trim(), req.getRefno(), req.getBillno(), req.getCancelBillno().trim(), resp);
+      if (ret) {
+        logger.info("url=[" + url + "]成功返回:" + _gson.toJson(resp));
+      } else {
+        print(url, resp);
+      }
+      return resp;
+    } catch (ReqErrorException rex) {
+      resp.error(RespCode.REQ_PARAM_ERROR, rex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (BusException bex) {
+      resp.error(RespCode.BUSINESS_PROCESS_ERROR, bex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      resp.error(RespCode.OTHER_ERROR, "system error!");
+      print(url, resp);
+      return resp;
+    }
+  }
+
+  /**
+   * 查询交易结果
+   */
+  @PostMapping("/queryResult")
+  public QrcodeQueryResultResp queryResult(@ModelAttribute QrcodeQueryResultReq req) {
+    String url = "/api/thirdpay/queryResult";
+    QrcodeQueryResultResp resp = new QrcodeQueryResultResp();
+    try {
+      req.checkParam();
+      TApiClient apiClient = qrcodeConsumeService.getApiClientByAppId(req.getPartner_id());
+      if (null == apiClient) {
+        throw new ReqErrorException("请求参数错误[合作伙伴ID不存在]");
+      }
+
+      if (!req.checkSign(apiClient.getSecret())) {
+        resp.error(RespCode.SIGN_CHECK_ERROR, "签名错误");
+        print(url, resp);
+        return resp;
+      }
+
+      boolean ret = qrcodeConsumeService.doQueryQRcodeTransdtlResult(req.getPartner_id().trim(), req.getRefno(), req.getBillno(), resp);
+      if (ret) {
+        logger.info("url=[" + url + "]成功返回:" + _gson.toJson(resp));
+      } else {
+        print(url, resp);
+      }
+      return resp;
+    } catch (ReqErrorException rex) {
+      resp.error(RespCode.REQ_PARAM_ERROR, rex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (BusException bex) {
+      resp.error(RespCode.BUSINESS_PROCESS_ERROR, bex.getMessage());
+      print(url, resp);
+      return resp;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      resp.error(RespCode.OTHER_ERROR, "system error!");
+      print(url, resp);
+      return resp;
+    }
+  }
+
+  /**
+   * 查询对账单
+   */
+  @PostMapping("/queryStatement")
+  public String queryStatement(@ModelAttribute QrcodeStatementReq req) {
+    String result = "";
+    String url = "/api/thirdpay/queryStatement";
+    try {
+      req.checkParam();
+      TApiClient apiClient = qrcodeConsumeService.getApiClientByAppId(req.getPartner_id());
+      if (null == apiClient) {
+        throw new ReqErrorException("请求参数错误[合作伙伴ID不存在]");
+      }
+
+      if (!req.checkSign(apiClient.getSecret())) {
+        result = _gson.toJson(new BusApiResp(RespCode.SIGN_CHECK_ERROR, "签名错误"));
+        logger.error("url=[" + url + "]返回错误:" + result);
+        return result;
+      }
+
+      String shopaccno = systemUtilService.getBusinessValue(BusConstant.QRCODE_THIRDPAY_SHOP_PREFIX + apiClient.getAppid().trim());
+      if (StringUtil.isEmpty(shopaccno)) {
+        result = _gson.toJson(new BusApiResp(RespCode.SHOP_NOT_CONFIG, "收费商户未配置"));
+        logger.error("url=[" + url + "]返回错误:" + result);
+        return result;
+      }
+      return qrcodeConsumeService.doGetChkdtls(req.getCheckDate().trim(), shopaccno.trim());
+    } catch (ReqErrorException rex) {
+      result = _gson.toJson(new BusApiResp(RespCode.REQ_PARAM_ERROR, rex.getMessage()));
+      logger.error("url=[" + url + "]返回错误:" + result);
+      return result;
+    } catch (BusException bex) {
+      result = _gson.toJson(new BusApiResp(RespCode.BUSINESS_PROCESS_ERROR, bex.getMessage()));
+      logger.error("url=[" + url + "]返回错误:" + result);
+      return result;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      result = _gson.toJson(new BusApiResp(RespCode.OTHER_ERROR, "system error!"));
+      logger.error("url=[" + url + "]返回错误:" + result);
+      return result;
+    }
+  }
+
+
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/dao/ThirdpartQrcodeTransdtlDao.java b/src/main/java/com/supwisdom/dlpay/bus/dao/ThirdpartQrcodeTransdtlDao.java
new file mode 100644
index 0000000..e69d42f
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/dao/ThirdpartQrcodeTransdtlDao.java
@@ -0,0 +1,32 @@
+package com.supwisdom.dlpay.bus.dao;
+
+import com.supwisdom.dlpay.bus.domain.TThirdpartQrcodeTransdtl;
+import com.supwisdom.dlpay.bus.domain.TThirdpartQrcodeTransdtlPk;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Lock;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.jpa.repository.QueryHints;
+import org.springframework.stereotype.Repository;
+
+import javax.persistence.LockModeType;
+import javax.persistence.QueryHint;
+
+@Repository
+public interface ThirdpartQrcodeTransdtlDao extends JpaRepository<TThirdpartQrcodeTransdtl, TThirdpartQrcodeTransdtlPk> {
+
+  @Lock(LockModeType.PESSIMISTIC_WRITE)
+  @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "0")})
+  @Query(value = "select t from TThirdpartQrcodeTransdtl t where t.billno = ?1 and t.appid=?2")
+  TThirdpartQrcodeTransdtl getByBillnoWithLock(String billno, String appid);
+
+  long countByQrcode(String qrcode);
+
+  TThirdpartQrcodeTransdtl getByBillnoAndAppid(String billno, String appid);
+
+  TThirdpartQrcodeTransdtl getByRefnoAndAppid(String refno, String appid);
+
+  TThirdpartQrcodeTransdtl getByCancelBillnoAndAppid(String cancelBillno, String appid);
+
+  TThirdpartQrcodeTransdtl getByCancelRefnoAndAppid(String cancelRefno,String appid);
+
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/domain/TThirdpartQrcodeTransdtl.java b/src/main/java/com/supwisdom/dlpay/bus/domain/TThirdpartQrcodeTransdtl.java
new file mode 100644
index 0000000..885ee05
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/domain/TThirdpartQrcodeTransdtl.java
@@ -0,0 +1,222 @@
+package com.supwisdom.dlpay.bus.domain;
+
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "tb_thirdpart_qrcode_transdtl",
+    indexes = {@Index(name = "idx_third_qrcodedtl_refno", columnList = "refno,appid"),
+        @Index(name = "idx_third_qrcodedtl_qrcode", columnList = "qrcode")})
+@IdClass(TThirdpartQrcodeTransdtlPk.class)
+public class TThirdpartQrcodeTransdtl {
+  @Id
+  @Column(name = "billno", nullable = false, length = 32)
+  private String billno;
+
+  @Id
+  @Column(name = "appid", nullable = false, length = 20)
+  private String appid;
+
+  @Column(name = "cardno", length = 20)
+  private String cardno;
+
+  @Column(name = "AMOUNT", precision = 12)
+  private Long amount;
+
+  @Column(name = "transdate", length = 8)
+  private String transdate;
+
+  @Column(name = "transtime", length = 6)
+  private String transtime;
+
+  @Column(name = "qrcode", length = 600)
+  private String qrcode;
+
+  @Column(name = "tac", length = 100)
+  private String tac;
+
+  @Column(name = "termdesc", length = 200)
+  private String termdesc;
+
+  @Column(name = "shopaccno", length = 20)
+  private String shopaccno;
+
+  @Column(name = "status", length = 20)
+  private String status;
+
+  @Column(name = "offlineflag")
+  private boolean offlineflag;
+
+  @Column(name = "dtltype", length = 20)
+  private String dtltype;
+
+  @Column(name = "refno", length = 32)
+  private String refno;
+
+  @Column(name = "cancel_refno", length = 32)
+  private String cancelRefno;
+
+  @Column(name = "cancel_billno", length = 32)
+  private String cancelBillno;
+
+  @Column(name = "errcode", length = 20)
+  private String errcode;
+
+  @Column(name = "errmsg", length = 600)
+  private String errmsg;
+
+  @Column(name = "createtime", length = 14)
+  private String createtime;
+
+  public String getBillno() {
+    return billno;
+  }
+
+  public void setBillno(String billno) {
+    this.billno = billno;
+  }
+
+  public String getAppid() {
+    return appid;
+  }
+
+  public void setAppid(String appid) {
+    this.appid = appid;
+  }
+
+  public String getCardno() {
+    return cardno;
+  }
+
+  public void setCardno(String cardno) {
+    this.cardno = cardno;
+  }
+
+  public Long getAmount() {
+    return amount;
+  }
+
+  public void setAmount(Long amount) {
+    this.amount = amount;
+  }
+
+  public String getTransdate() {
+    return transdate;
+  }
+
+  public void setTransdate(String transdate) {
+    this.transdate = transdate;
+  }
+
+  public String getTranstime() {
+    return transtime;
+  }
+
+  public void setTranstime(String transtime) {
+    this.transtime = transtime;
+  }
+
+  public String getQrcode() {
+    return qrcode;
+  }
+
+  public void setQrcode(String qrcode) {
+    this.qrcode = qrcode;
+  }
+
+  public String getTac() {
+    return tac;
+  }
+
+  public void setTac(String tac) {
+    this.tac = tac;
+  }
+
+  public String getTermdesc() {
+    return termdesc;
+  }
+
+  public void setTermdesc(String termdesc) {
+    this.termdesc = termdesc;
+  }
+
+  public String getShopaccno() {
+    return shopaccno;
+  }
+
+  public void setShopaccno(String shopaccno) {
+    this.shopaccno = shopaccno;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  public boolean isOfflineflag() {
+    return offlineflag;
+  }
+
+  public void setOfflineflag(boolean offlineflag) {
+    this.offlineflag = offlineflag;
+  }
+
+  public String getDtltype() {
+    return dtltype;
+  }
+
+  public void setDtltype(String dtltype) {
+    this.dtltype = dtltype;
+  }
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  public String getCancelRefno() {
+    return cancelRefno;
+  }
+
+  public void setCancelRefno(String cancelRefno) {
+    this.cancelRefno = cancelRefno;
+  }
+
+  public String getCancelBillno() {
+    return cancelBillno;
+  }
+
+  public void setCancelBillno(String cancelBillno) {
+    this.cancelBillno = cancelBillno;
+  }
+
+  public String getErrcode() {
+    return errcode;
+  }
+
+  public void setErrcode(String errcode) {
+    this.errcode = errcode;
+  }
+
+  public String getErrmsg() {
+    return errmsg;
+  }
+
+  public void setErrmsg(String errmsg) {
+    this.errmsg = errmsg;
+  }
+
+  public String getCreatetime() {
+    return createtime;
+  }
+
+  public void setCreatetime(String createtime) {
+    this.createtime = createtime;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/domain/TThirdpartQrcodeTransdtlPk.java b/src/main/java/com/supwisdom/dlpay/bus/domain/TThirdpartQrcodeTransdtlPk.java
new file mode 100644
index 0000000..64bbe55
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/domain/TThirdpartQrcodeTransdtlPk.java
@@ -0,0 +1,59 @@
+package com.supwisdom.dlpay.bus.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Id;
+import java.io.Serializable;
+
+public class TThirdpartQrcodeTransdtlPk implements Serializable {
+  @Id
+  @Column(name = "billno", nullable = false, length = 32)
+  private String billno;
+
+  @Id
+  @Column(name = "appid", nullable = false, length = 20)
+  private String appid;
+
+  public TThirdpartQrcodeTransdtlPk() {
+  }
+
+  public TThirdpartQrcodeTransdtlPk(String billno, String appid) {
+    this.billno = billno;
+    this.appid = appid;
+  }
+
+  public String getBillno() {
+    return billno;
+  }
+
+  public void setBillno(String billno) {
+    this.billno = billno;
+  }
+
+  public String getAppid() {
+    return appid;
+  }
+
+  public void setAppid(String appid) {
+    this.appid = appid;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    TThirdpartQrcodeTransdtlPk thirdpartQrcodeTransdtlPk = (TThirdpartQrcodeTransdtlPk) o;
+    if (billno != null ? !billno.equals(thirdpartQrcodeTransdtlPk.getBillno()) : billno != null)
+      return false;
+    if (appid != null ? !appid.equals(thirdpartQrcodeTransdtlPk.getAppid()) : appid != null)
+      return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = billno != null ? billno.hashCode() : 0;
+    result = 31 * result + (appid != null ? appid.hashCode() : 0);
+    return result;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/service/QrcodeConsumeService.java b/src/main/java/com/supwisdom/dlpay/bus/service/QrcodeConsumeService.java
new file mode 100644
index 0000000..4176f9b
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/service/QrcodeConsumeService.java
@@ -0,0 +1,38 @@
+package com.supwisdom.dlpay.bus.service;
+
+import com.supwisdom.dlpay.bus.bean.QrcodePayCancelResp;
+import com.supwisdom.dlpay.bus.bean.QrcodePayResp;
+import com.supwisdom.dlpay.bus.bean.QrcodeQueryResultResp;
+import com.supwisdom.dlpay.bus.domain.TThirdpartQrcodeTransdtl;
+import com.supwisdom.dlpay.framework.domain.TApiClient;
+import org.springframework.transaction.annotation.Transactional;
+
+public interface QrcodeConsumeService {
+  @Transactional(rollbackFor = Exception.class, readOnly = true)
+  TApiClient getApiClientByAppId(String appid);
+
+  @Transactional(rollbackFor = Exception.class, readOnly = true)
+  String getPayapiQRCodeRootKey(String enckey, String timestamp) throws Exception;
+
+  @Transactional(rollbackFor = Exception.class)
+  TThirdpartQrcodeTransdtl saveQRcodeTransdtl(String appid, String billno, String cardno, long amount, String transdate, String transtime,
+                                              String qrcode, String tac, String termdesc, String shopaccno, boolean offlineflag) throws Exception;
+
+  @Transactional(rollbackFor = Exception.class)
+  boolean doSendQRcodePayInit(TThirdpartQrcodeTransdtl transdtl, QrcodePayResp resp) throws Exception;
+
+  @Transactional(rollbackFor = Exception.class)
+  boolean doConfirmQRcodePay(String appid, String refno, String billno, QrcodePayResp resp) throws Exception;
+
+  @Transactional(rollbackFor = Exception.class)
+  boolean doCancelQRcodeTransdtl(String appid, String refno, String billno, String cancelBillno, QrcodePayCancelResp resp) throws Exception;
+
+  @Transactional(rollbackFor = Exception.class)
+  boolean doQueryQRcodeTransdtlResult(String appid, String refno, String billno, QrcodeQueryResultResp resp) throws Exception;
+
+  String doGetChkdtls(String chkdate, String shopaccno) throws Exception;
+
+  String doQueryQRcode(String uid, String userid) throws Exception;
+
+
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/service/impl/QrcodeConsumeServiceImpl.java b/src/main/java/com/supwisdom/dlpay/bus/service/impl/QrcodeConsumeServiceImpl.java
new file mode 100644
index 0000000..86c90d2
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/service/impl/QrcodeConsumeServiceImpl.java
@@ -0,0 +1,375 @@
+package com.supwisdom.dlpay.bus.service.impl;
+
+import com.supwisdom.dlpay.api.bean.*;
+import com.supwisdom.dlpay.bus.bean.QrcodePayCancelResp;
+import com.supwisdom.dlpay.bus.bean.QrcodePayResp;
+import com.supwisdom.dlpay.bus.bean.QrcodeQueryResultResp;
+import com.supwisdom.dlpay.bus.dao.ThirdpartQrcodeTransdtlDao;
+import com.supwisdom.dlpay.bus.domain.TThirdpartQrcodeTransdtl;
+import com.supwisdom.dlpay.bus.service.QrcodeConsumeService;
+import com.supwisdom.dlpay.bus.util.*;
+import com.supwisdom.dlpay.framework.dao.ApiClientDao;
+import com.supwisdom.dlpay.framework.data.SystemDateTime;
+import com.supwisdom.dlpay.framework.domain.TApiClient;
+import com.supwisdom.dlpay.framework.service.SystemUtilService;
+import com.supwisdom.dlpay.framework.util.MoneyUtil;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+import com.supwisdom.dlpay.framework.util.TradeDict;
+import com.supwisdom.dlpay.paysdk.proxy.CitizenCardPayProxy;
+import com.supwisdom.dlpay.paysdk.proxy.ShopProxy;
+import com.supwisdom.dlpay.paysdk.proxy.TransactionProxy;
+import com.supwisdom.dlpay.paysdk.proxy.UserProxy;
+import com.supwisdom.dlpay.util.AesUtil;
+import org.apache.commons.codec.binary.Base64;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Map;
+
+@Service
+public class QrcodeConsumeServiceImpl implements QrcodeConsumeService {
+  @Autowired
+  private ApiClientDao apiClientDao;
+  @Autowired
+  private ThirdpartQrcodeTransdtlDao thirdpartQrcodeTransdtlDao;
+  @Autowired
+  private SystemUtilService systemUtilService;
+
+  @Autowired
+  private CitizenCardPayProxy citizenCardPayProxy;
+  @Autowired
+  private TransactionProxy transactionProxy;
+  @Autowired
+  private ShopProxy shopProxy;
+  @Autowired
+  private UserProxy userProxy;
+
+  private String expMsg(String exp) {
+    return StringUtil.isEmpty(exp) ? "" : "|" + exp.trim();
+  }
+
+  @Override
+  public TApiClient getApiClientByAppId(String appid) {
+    return StringUtil.isEmpty(appid) ? null : apiClientDao.findByAppid(appid.trim());
+  }
+
+  @Override
+  public String getPayapiQRCodeRootKey(String enckey, String timestamp) throws Exception {
+    CitizenQrcodeKeyParam param = new CitizenQrcodeKeyParam();
+    param.setTimestamp(timestamp);
+    CitizenQrcodeKey qrcodeKey = citizenCardPayProxy.queryQrcodeKey(param);
+    if (qrcodeKey.getRetcode() != 0) {
+      throw new BusException(qrcodeKey.getRetmsg() + expMsg(qrcodeKey.getException()));
+    }
+
+    byte[] bKey = Base64.decodeBase64(qrcodeKey.getRootKey());
+    String keyBuffer = String.format("%s%04x%02x", HexUtil.encodeHex(bKey), qrcodeKey.getTotpStep() & 0xFFFF, qrcodeKey.getTotpOffset() & 0xFF);
+    String key = Base64.encodeBase64URLSafeString(HexUtil.decodeHex(keyBuffer)); //rootkey + step + offset
+
+    final String json = "{\"rootKey\":\"" + key + "\",\"iv\":\"" + qrcodeKey.getIv() + "\",\"offsetSec\":\"" + qrcodeKey.getOffsetSec() + "\"}";
+    final String encData = AesUtil.encrypt(json, enckey); //用appKey加密
+//    final String decData = AesUtil.decrypt(encData, enckey);
+    return encData;
+  }
+
+  @Override
+  public TThirdpartQrcodeTransdtl saveQRcodeTransdtl(String appid, String billno, String cardno, long amount, String transdate, String transtime, String qrcode, String tac, String termdesc, String shopaccno, boolean offlineflag) throws Exception {
+    TThirdpartQrcodeTransdtl transdtl = thirdpartQrcodeTransdtlDao.getByBillnoWithLock(billno, appid);
+    if (null != transdtl) {
+      if (cardno.equals(transdtl.getCardno()) && amount == transdtl.getAmount() && transdate.equals(transdtl.getTransdate())
+          && transtime.equals(transdtl.getTranstime()) && qrcode.equals(transdtl.getQrcode()) && tac.equals(transdtl.getTac())) {
+        if (BusConstant.STATUS_TRANSDTL_INIT.equals(transdtl.getStatus()) && StringUtil.isEmpty(transdtl.getRefno())) {
+          return transdtl; //重复请求
+        } else {
+          throw new BusException("该商户订单已提交交易请求,请查询结果!");
+        }
+      }
+      throw new BusException("商户订单号重复!");
+    }
+
+    if (thirdpartQrcodeTransdtlDao.countByQrcode(qrcode) > 0) {
+      throw new BusException("二维码已使用!");
+    }
+
+    transdtl = new TThirdpartQrcodeTransdtl();
+    transdtl.setBillno(billno);
+    transdtl.setAppid(appid);
+    transdtl.setCardno(cardno);
+    transdtl.setAmount(amount);
+    transdtl.setTransdate(transdate);
+    transdtl.setTranstime(transtime);
+    transdtl.setQrcode(qrcode);
+    transdtl.setTac(tac);
+    transdtl.setTermdesc(termdesc);
+    transdtl.setShopaccno(shopaccno);
+    transdtl.setStatus(BusConstant.STATUS_TRANSDTL_INIT);
+    transdtl.setDtltype("carbus"); //fixme: 可根据appid切换
+    transdtl.setOfflineflag(offlineflag);
+    transdtl.setCreatetime(systemUtilService.getSysdatetime().getHostdatetime());
+    thirdpartQrcodeTransdtlDao.save(transdtl);
+    return transdtl;
+  }
+
+
+  @Override
+  public boolean doSendQRcodePayInit(TThirdpartQrcodeTransdtl transdtl, QrcodePayResp resp) throws Exception {
+    resp.setRetcode(RespCode.REQ_PAYAPI_ERROR);
+
+    CitizenQrcodePayinitParam param = new CitizenQrcodePayinitParam();
+    param.setQrcode(transdtl.getQrcode());
+    param.setQrcodeType(transdtl.isOfflineflag() ? "offline" : "online");
+    param.setShopaccno(transdtl.getShopaccno());
+    param.setAmount(transdtl.getAmount().intValue());
+    param.setBillno(transdtl.getBillno());
+    param.setTransdate(transdtl.getTransdate());
+    param.setTranstime(transdtl.getTranstime());
+    param.setDtltype(transdtl.getDtltype());
+    param.setCardNo(transdtl.getCardno());
+    param.setTac(transdtl.getTac());
+
+    CitizenPayResponse response = citizenCardPayProxy.citizencardQrcodePayinit(param);
+    if (response.getRetcode() != 0) {
+      transdtl.setErrcode(RespCode.BUSINESS_PROCESS_ERROR);
+      transdtl.setErrmsg("向核心平台初始化下单失败!" + response.getRetmsg() + expMsg(response.getException()));
+      thirdpartQrcodeTransdtlDao.save(transdtl);
+
+      resp.setRetmsg(response.getRetmsg()+expMsg(response.getException()));
+      return false;
+    }
+
+    transdtl.setRefno(response.getRefno());
+    transdtl.setErrcode("0");
+    transdtl.setErrmsg("向核心平台初始化下单成功!");
+    thirdpartQrcodeTransdtlDao.save(transdtl);
+
+    resp.setRetcode(RespCode.SUCCESS);
+    resp.setRetmsg("success");
+    resp.setAmount(MoneyUtil.YuanToFen(response.getAmount()));
+    resp.setBillno(response.getBillno());
+    resp.setRefno(response.getRefno());
+    return true;
+  }
+
+  @Override
+  public boolean doConfirmQRcodePay(String appid, String refno, String billno, QrcodePayResp resp) throws Exception {
+    TThirdpartQrcodeTransdtl transdtl = null;
+    if (!StringUtil.isEmpty(billno)) {
+      transdtl = thirdpartQrcodeTransdtlDao.getByBillnoAndAppid(billno.trim(), appid);
+      if (null != transdtl && !StringUtil.isEmpty(refno) && !refno.trim().equals(transdtl.getRefno())) {
+        throw new ReqErrorException("请求参数错误[商户订单号与平台流水号不一致]");
+      }
+    } else {
+      transdtl = thirdpartQrcodeTransdtlDao.getByRefnoAndAppid(refno.trim(), appid);
+    }
+
+    if (null == transdtl) {
+      throw new ReqErrorException("请求参数错误[流水号错误,流水不存在]");
+    } else if (StringUtil.isEmpty(transdtl.getRefno())) {
+      throw new BusException("订单初始化失败!");
+    } else if (Arrays.asList(TradeDict.DTL_STATUS_SUCCESS, TradeDict.DTL_STATUS_FAIL).contains(transdtl.getStatus())) {
+      throw new BusException("订单已经确认过,请直接查询结果!");
+    }
+
+    CitizenCardPayfinishParam param = new CitizenCardPayfinishParam();
+    param.setRefno(transdtl.getRefno());
+
+    CitizenPayResponse response = citizenCardPayProxy.citizencardPayFinish(param);
+    if (response.getRetcode() != 0) {
+      //失败
+      if (response.getRetcode() == 55555) {
+        transdtl.setStatus(TradeDict.DTL_STATUS_WIP);
+        transdtl.setErrcode(RespCode.USER_PAYING_ERROR);
+        transdtl.setErrmsg("用户支付中,请稍后查询流水结果");
+      } else {
+        transdtl.setStatus(TradeDict.DTL_STATUS_FAIL);
+        transdtl.setErrcode(RespCode.USER_PAY_FAIL);
+        transdtl.setErrmsg(response.getRetmsg()+expMsg(response.getException()));
+      }
+      thirdpartQrcodeTransdtlDao.save(transdtl);
+      resp.error(transdtl.getErrcode(), transdtl.getErrmsg());
+      return false;
+    }
+
+    //扣款成功
+    transdtl.setStatus(TradeDict.DTL_STATUS_SUCCESS);
+    transdtl.setErrcode("0");
+    transdtl.setErrmsg(null);
+    thirdpartQrcodeTransdtlDao.save(transdtl);
+
+    resp.setRetcode(RespCode.SUCCESS);
+    resp.setRetmsg("success");
+    resp.setBillno(response.getBillno());
+    resp.setRefno(response.getRefno());
+    resp.setAmount(MoneyUtil.YuanToFen(response.getAmount()));
+    return true;
+  }
+
+  @Override
+  public boolean doCancelQRcodeTransdtl(String appid, String refno, String billno, String cancelBillno, QrcodePayCancelResp resp) throws Exception {
+    TThirdpartQrcodeTransdtl transdtl = null;
+    if (!StringUtil.isEmpty(billno)) {
+      transdtl = thirdpartQrcodeTransdtlDao.getByBillnoAndAppid(billno.trim(), appid);
+      if (null != transdtl && !StringUtil.isEmpty(refno) && !refno.trim().equals(transdtl.getRefno())) {
+        throw new ReqErrorException("请求参数错误[商户订单号与平台流水号不一致]");
+      }
+    } else {
+      transdtl = thirdpartQrcodeTransdtlDao.getByRefnoAndAppid(refno.trim(), appid);
+    }
+
+    if (null == transdtl) {
+      throw new ReqErrorException("请求参数错误[流水号错误,原始交易流水不存在]");
+    } else if (StringUtil.isEmpty(transdtl.getRefno())) {
+      throw new BusException("原始交易订单初始化失败!");
+    } else if (null != transdtl.getCancelBillno() && !cancelBillno.equals(transdtl.getCancelBillno())) {
+      throw new BusException("原始交易订单已提交过撤销!已有撤销单号:" + transdtl.getCancelBillno());
+    }
+
+    SystemDateTime dt = systemUtilService.getSysdatetime();
+    ConsumePayCancelParam param = new ConsumePayCancelParam();
+    param.setBillno(transdtl.getBillno());
+    param.setRefno(transdtl.getRefno());
+    param.setShopaccno(transdtl.getShopaccno());
+    param.setRequestbillno(cancelBillno);
+    param.setTransdate(dt.getHostdate());
+    param.setTranstime(dt.getHosttime());
+
+    PayReverseResponse response = transactionProxy.payCancel(param);
+    transdtl.setCancelBillno(cancelBillno);
+    if (!StringUtil.isEmpty(response.getRefno())) {
+      transdtl.setCancelRefno(response.getRefno());
+    }
+    if (response.getRetcode() != 0) {
+      //失败
+      if (response.getRetcode() == 55555) {
+        transdtl.setErrcode(RespCode.CANCEL_PAYING_ERROR);
+        transdtl.setErrmsg("撤销或退款处理中,请稍后查询处理结果");
+      } else {
+        transdtl.setErrcode(RespCode.CANCEL_FAIL);
+        transdtl.setErrmsg("撤销失败!" + response.getRetmsg()+expMsg(response.getException()));
+      }
+      thirdpartQrcodeTransdtlDao.save(transdtl);
+      resp.error(transdtl.getErrcode(), transdtl.getErrmsg());
+      return false;
+    }
+
+    //成功
+    transdtl.setStatus(TradeDict.DTL_STATUS_CANCEL);
+    transdtl.setErrcode("00");
+    transdtl.setErrmsg("撤销成功!");
+    thirdpartQrcodeTransdtlDao.save(transdtl);
+
+    resp.setRetcode(RespCode.SUCCESS);
+    resp.setRetmsg("撤销成功!");
+    resp.setRefno(response.getRefno());
+    return true;
+  }
+
+  @Override
+  public boolean doQueryQRcodeTransdtlResult(String appid, String refno, String billno, QrcodeQueryResultResp resp) throws Exception {
+    boolean revflag = false;
+    TThirdpartQrcodeTransdtl transdtl = null;
+    if (!StringUtil.isEmpty(billno)) {
+      transdtl = thirdpartQrcodeTransdtlDao.getByBillnoAndAppid(billno.trim(), appid);
+      if (null != transdtl && !StringUtil.isEmpty(refno) && !StringUtil.isEmpty(transdtl.getRefno())
+          && !refno.trim().equals(transdtl.getRefno())) {
+        throw new ReqErrorException("请求参数错误[商户订单号与平台流水号不一致]");
+      }
+    } else {
+      transdtl = thirdpartQrcodeTransdtlDao.getByRefnoAndAppid(refno.trim(), appid);
+    }
+
+    if (null == transdtl) {
+      if (!StringUtil.isEmpty(billno)) {
+        transdtl = thirdpartQrcodeTransdtlDao.getByCancelBillnoAndAppid(billno, appid);
+        if (null != transdtl && !StringUtil.isEmpty(refno) && !StringUtil.isEmpty(transdtl.getCancelRefno())
+            && !refno.trim().equals(transdtl.getCancelRefno())) {
+          throw new ReqErrorException("请求参数错误[商户订单号与平台流水号不一致]");
+        }
+      } else {
+        transdtl = thirdpartQrcodeTransdtlDao.getByCancelRefnoAndAppid(refno.trim(), appid);
+      }
+      if (null != transdtl) revflag = true; //查询的撤销流水
+    }
+
+    if (null == transdtl) {
+      throw new ReqErrorException("请求参数错误[流水号错误,交易流水不存在]");
+    }
+
+    QueryDtlResultParam param = new QueryDtlResultParam();
+    param.setBillno(null != billno ? billno.trim() : null);
+    param.setRefno(null != refno ? refno.trim() : null);
+    param.setShopaccno(transdtl.getShopaccno());
+
+    QueryTransDtlResponse response = transactionProxy.queryDtlResult(param);
+    if (response.getRetcode() != 0) {
+      resp.error(RespCode.BUSINESS_PROCESS_ERROR, response.getRetmsg()+expMsg(response.getException()));
+      return false;
+    }
+
+    if ("success".equals(response.getStatus())) {
+      //明确流水成功,修改一些状态
+      if (revflag && !TradeDict.DTL_STATUS_CANCEL.equals(transdtl.getStatus())) {
+        transdtl.setStatus(TradeDict.DTL_STATUS_CANCEL);
+        transdtl.setErrcode("00");
+        transdtl.setErrmsg("撤销成功!");
+        thirdpartQrcodeTransdtlDao.save(transdtl);
+      } else if (!revflag && !TradeDict.DTL_STATUS_SUCCESS.equals(transdtl.getStatus())) {
+        transdtl.setStatus(TradeDict.DTL_STATUS_SUCCESS);
+        transdtl.setErrcode("0");
+        transdtl.setErrmsg(null);
+        thirdpartQrcodeTransdtlDao.save(transdtl);
+      }
+    } else if ("fail".equals(response.getStatus())) {
+      //明确流水失败
+      if (revflag) {
+        transdtl.setErrcode(RespCode.CANCEL_FAIL);
+        transdtl.setErrmsg("撤销失败!" + expMsg(response.getRemark()));
+        thirdpartQrcodeTransdtlDao.save(transdtl);
+      } else {
+        transdtl.setStatus(TradeDict.DTL_STATUS_FAIL);
+        transdtl.setErrcode(RespCode.USER_PAY_FAIL);
+        transdtl.setErrmsg("查询结果为失败!" + expMsg(response.getRemark()));
+        thirdpartQrcodeTransdtlDao.save(transdtl);
+      }
+    }
+
+    resp.setRetcode(RespCode.SUCCESS);
+    resp.setRetmsg("success");
+    resp.setBillno(response.getOutTradeNo());
+    resp.setRefno(response.getRefno());
+    resp.setAmount(MoneyUtil.YuanToFen(response.getAmount()));
+    resp.setStatus(response.getStatus());
+    resp.setCardno(response.getPayinfo());
+    resp.setSourceType(response.getSourceType());
+    resp.setReverseFlag(response.getReverseFlag());
+    resp.setTransdesc(response.getTransdesc());
+    resp.setRemark(response.getRemark());
+    resp.setTermdesc(transdtl.getTermdesc());
+    return true;
+  }
+
+  @Override
+  public String doGetChkdtls(String chkdate, String shopaccno) throws Exception {
+    DownloadShopBillParam param = new DownloadShopBillParam();
+    param.setCheckdate(chkdate);
+    param.setShopaccno(shopaccno);
+    return shopProxy.downloadShopBill(param);
+  }
+
+  @Override
+  public String doQueryQRcode(String uid, String userid) throws Exception {
+    QrcodeParam param = new QrcodeParam();
+    param.setUid(uid);
+    param.setUserid(userid);
+
+    Map<String, Object> response = userProxy.qrcode(param);
+    final String retcode = response.get("retcode") != null ? response.get("retcode").toString() : "99";
+    final String retmsg = response.get("retmsg") != null ? response.get("retmsg").toString() : "请求生成二维码失败!";
+    if ("0".equals(retcode)) {
+      return response.get("qrcode").toString();
+    }
+
+    throw new ReqErrorException(retmsg);
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/task/BusPayTask.java b/src/main/java/com/supwisdom/dlpay/bus/task/BusPayTask.java
index 235277a..149a0cd 100644
--- a/src/main/java/com/supwisdom/dlpay/bus/task/BusPayTask.java
+++ b/src/main/java/com/supwisdom/dlpay/bus/task/BusPayTask.java
@@ -17,6 +17,7 @@
 import com.supwisdom.dlpay.restaurant.service.OfflineTransDtlService;
 import com.supwisdom.dlpay.restaurant.service.TransDtlService;
 import com.supwisdom.dlpay.restaurant.util.RestaurantConstant;
+import net.javacrumbs.shedlock.core.SchedulerLock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,7 +48,8 @@
     @Autowired
     private TransactionProxy transactionProxy;
 
-    @Scheduled(cron = "0 0/1 * * * ?")
+    @Scheduled(cron = "${busapp.upload.transdtl.task.cron}")
+    @SchedulerLock(name = "BuspayUploadTransdtlTask", lockAtMostForString = "PT10M")
     private void submitOfflineDtlTask() {
         String date = DateUtil.getNow("yyyyMMdd");
         List<TOfflineBusdtl> dtls = busConsumeService.getUncheckOfflineTransdtl(date);
diff --git a/src/main/java/com/supwisdom/dlpay/bus/util/BusConstant.java b/src/main/java/com/supwisdom/dlpay/bus/util/BusConstant.java
index da255ac..d6ba66d 100644
--- a/src/main/java/com/supwisdom/dlpay/bus/util/BusConstant.java
+++ b/src/main/java/com/supwisdom/dlpay/bus/util/BusConstant.java
@@ -1,10 +1,10 @@
 package com.supwisdom.dlpay.bus.util;
 
 public class BusConstant {
-  public final static String PAYAPI_CONNECT_VERSION="1.0";
-  public final static String PAYAPI_CONNECT_SIGNTYPE="MD5";
-  public final static String PAYAPI_CONNECT_TENANTID="tenantid";
-  public final static String PAYAPI_CONNECT_SIGNKEY="c9ae5b4fe5014b5b9eb19e1a0797a3a3";
+  public final static String PAYAPI_CONNECT_VERSION = "1.0";
+  public final static String PAYAPI_CONNECT_SIGNTYPE = "MD5";
+  public final static String PAYAPI_CONNECT_TENANTID = "tenantid";
+  public final static String PAYAPI_CONNECT_SIGNKEY = "c9ae5b4fe5014b5b9eb19e1a0797a3a3";
 
   public final static String PAYAPI_CARDSYNC_DEFAULTTIME = "00000000000000"; //yyyyMMddhhmmss
 
@@ -12,10 +12,10 @@
   public final static String CARDVERNO_DEFAULT = "00000000000000"; //yyMMdd+8位递增数
 
 
-  public final static String STATUS_CARDVER_NORMAL="normal";
-  public final static String STATUS_CARDVER_CLOSED="closed";
+  public final static String STATUS_CARDVER_NORMAL = "normal";
+  public final static String STATUS_CARDVER_CLOSED = "closed";
 
-  public final static String PAYAPI_CARDSYNC_ONCECOUNT="payapi.cardsync.oncecount";
+  public final static String PAYAPI_CARDSYNC_ONCECOUNT = "payapi.cardsync.oncecount";
   public final static int PAYAPI_CARDSYNC_ONCECOUNT_DEFAULT = 20000;
 
 
@@ -33,10 +33,10 @@
   public final static int SYSPARAID_MAXBLACKCARDVERNO = 11; //全局参数,最大黑名单版本号
   public final static int SYSPARAID_CARDSYNCTIME = 21; //全局参数,卡同步时间
 
-  public final static String STATUS_NORMAL="normal";
-  public final static String STATUS_INIT="init";
-  public final static String STATUS_WAIT="wip";
-  public final static String TRANSMODE_BUS="bus";
+  public final static String STATUS_NORMAL = "normal";
+  public final static String STATUS_INIT = "init";
+  public final static String STATUS_WAIT = "wip";
+  public final static String TRANSMODE_BUS = "bus";
 
   public final static String BUS_OFFLINE_NODEVICE = "11";
   public final static String BUS_OFFLINE_NOUSER = "12";
@@ -55,7 +55,9 @@
   public static final String STATUS_TRANSDTL_WAIT = "wip";   //提交中
   public static final String STATUS_TRANSDTL_SUCCESS = "success";   //已入账
   public static final String STATUS_TRANSDTL_FAIL = "fail";   //取消
-  public static final String  TRANSTYPE_TRANSDTL_REVERT = "revert";   //取消
+  public static final String TRANSTYPE_TRANSDTL_REVERT = "revert";   //取消
 
 
+  public final static String QRCODE_THIRDPAY_SHOP_PREFIX = "thirdpart.qrcode.shopaccno.";
+
 }
diff --git a/src/main/java/com/supwisdom/dlpay/bus/util/HexUtil.java b/src/main/java/com/supwisdom/dlpay/bus/util/HexUtil.java
new file mode 100644
index 0000000..8fea8c5
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/util/HexUtil.java
@@ -0,0 +1,25 @@
+package com.supwisdom.dlpay.bus.util;
+
+public class HexUtil {
+  public static byte[] decodeHex(String data) {
+    if (data == null || data.length() % 2 != 0) {
+      throw new RuntimeException("decodeHex data length must be divided by 2");
+    }
+    byte[] result = new byte[data.length() / 2];
+    for (int i = 0; i < data.length(); i += 2) {
+      result[i / 2] = (byte) Integer.parseInt(data.substring(i, i + 2), 16);
+    }
+    return result;
+  }
+
+  public static String encodeHex(byte[] data) {
+    if (data == null) {
+      throw new RuntimeException("encodeHex data must be not null");
+    }
+    StringBuilder sb = new StringBuilder();
+    for (byte datum : data) {
+      sb.append(String.format("%02x", datum & 0xFF));
+    }
+    return sb.toString();
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/util/ReqErrorException.java b/src/main/java/com/supwisdom/dlpay/bus/util/ReqErrorException.java
new file mode 100644
index 0000000..0de642c
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/util/ReqErrorException.java
@@ -0,0 +1,7 @@
+package com.supwisdom.dlpay.bus.util;
+
+public class ReqErrorException extends Exception {
+  public ReqErrorException(String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/bus/util/RespCode.java b/src/main/java/com/supwisdom/dlpay/bus/util/RespCode.java
new file mode 100644
index 0000000..a812295
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/bus/util/RespCode.java
@@ -0,0 +1,18 @@
+package com.supwisdom.dlpay.bus.util;
+
+public class RespCode {
+  public static final String SUCCESS="0";
+
+  public static final String BUSINESS_PROCESS_ERROR = "30"; //业务处理过程中的错误
+  public static final String REQ_PARAM_ERROR = "31"; //请求参数错误
+  public static final String SHOP_NOT_CONFIG = "32"; //收费商户未配置
+  public static final String REQ_PAYAPI_ERROR = "33"; //请求核心平台返回错误
+  public static final String SIGN_CHECK_ERROR = "34";  //签名验证错误
+  public static final String USER_PAYING_ERROR = "35";  //用户支付中
+  public static final String USER_PAY_FAIL = "36";  //扣费败失
+  public static final String CANCEL_PAYING_ERROR = "37";  //撤销或退款处理中
+  public static final String CANCEL_FAIL = "38";  //撤销或退款处理中
+
+  public static final String OTHER_ERROR = "99"; //其他错误
+
+}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/util/StringUtil.java b/src/main/java/com/supwisdom/dlpay/framework/util/StringUtil.java
index c642200..903bcca 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/util/StringUtil.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/util/StringUtil.java
@@ -148,5 +148,20 @@
         return true;
     }
 
+    public static Map<String, String> swParaFilter(Map<String, String> sArray) {
+        Map<String, String> result = new HashMap<String, String>();
+        if (sArray == null || sArray.size() <= 0) {
+            return result;
+        }
+        for (String key : sArray.keySet()) {
+            String value = sArray.get(key);
+            if (null == value || "".equals(value.trim()) || key.equalsIgnoreCase("sign")) {
+                continue;
+            }
+            result.put(key, value);
+        }
+        return result;
+    }
+
 
 }
diff --git a/src/main/java/com/supwisdom/dlpay/restaurant/task/RestaurantTask.java b/src/main/java/com/supwisdom/dlpay/restaurant/task/RestaurantTask.java
index 3c9e0a2..c02b8a9 100644
--- a/src/main/java/com/supwisdom/dlpay/restaurant/task/RestaurantTask.java
+++ b/src/main/java/com/supwisdom/dlpay/restaurant/task/RestaurantTask.java
@@ -15,6 +15,7 @@
 import com.supwisdom.dlpay.restaurant.service.OfflineTransDtlService;
 import com.supwisdom.dlpay.restaurant.service.TransDtlService;
 import com.supwisdom.dlpay.restaurant.util.RestaurantConstant;
+import net.javacrumbs.shedlock.core.SchedulerLock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -40,7 +41,8 @@
     @Autowired
     private UserProxy userProxy;
 
-    @Scheduled(cron = "0 0/1 * * * ? ")
+    @Scheduled(cron = "${restaurant.sync.card.task.cron}")
+    @SchedulerLock(name = "RestaurantSyncCardTask", lockAtMostForString = "PT20M")
     private void CustomerCheckTask(){
         CustomerSearchBean searchBean=new CustomerSearchBean();
         searchBean.setCheckstatus(RestaurantConstant.STATUS_CHECKSTATUS_UNCHECK);
diff --git a/src/main/java/com/supwisdom/dlpay/util/AesUtil.java b/src/main/java/com/supwisdom/dlpay/util/AesUtil.java
new file mode 100644
index 0000000..c3e938b
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/util/AesUtil.java
@@ -0,0 +1,161 @@
+package com.supwisdom.dlpay.util;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+public class AesUtil {
+
+	public static String encrypt(String rawdata, String secret) {
+		Cipher cipher;
+		try {
+			cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+			SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES");
+			cipher.init(Cipher.ENCRYPT_MODE, key);
+			byte[] encdata = cipher.doFinal(rawdata.getBytes("UTF-8"));
+			String hexdata = Base64.encodeBase64String(encdata);
+			
+			return hexdata;
+		} catch (NoSuchAlgorithmException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidKeyException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalBlockSizeException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (BadPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return null;
+		
+	}
+	public static String encryptCFB(String rawdata, String secret ,String ivstr,String KEY_ALG_MODE) {
+		Cipher cipher;
+		try {
+			//byte[] iv = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
+			byte[] iv = Hex.decodeHex(ivstr.toCharArray());
+			IvParameterSpec ivSpec = new IvParameterSpec(iv);
+			cipher = Cipher.getInstance(KEY_ALG_MODE);
+			SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(secret), "AES");
+			cipher.init(Cipher.ENCRYPT_MODE, key,ivSpec);
+			byte[] encdata = cipher.doFinal(rawdata.getBytes("UTF-8"));
+			String hexdata = Base64.encodeBase64String(encdata);
+
+			return hexdata;
+		} catch (NoSuchAlgorithmException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidKeyException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalBlockSizeException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (BadPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidAlgorithmParameterException e) {
+			e.printStackTrace();
+		} catch (DecoderException e) {
+			e.printStackTrace();
+		}
+		return null;
+
+	}
+	
+	public static String decrypt(String encdata, String secret){
+		Cipher cipher;
+		try {
+			cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+			SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES");
+			cipher.init(Cipher.DECRYPT_MODE, key);
+			byte[] decordedValue = Base64.decodeBase64(encdata);
+			byte[] decdata = cipher.doFinal(decordedValue);
+			String rawdata = new String(decdata);
+			
+			return rawdata;
+		} catch (NoSuchAlgorithmException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidKeyException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalBlockSizeException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (BadPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	public static String decryptCFB(String encdata, String secret,String ivstr,String KEY_ALG_MODE){
+		Cipher cipher;
+		try {
+//			byte[] iv = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
+			byte[] iv = Hex.decodeHex(ivstr.toCharArray());
+			IvParameterSpec ivSpec = new IvParameterSpec(iv);
+			cipher = Cipher.getInstance(KEY_ALG_MODE);
+			SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(secret), "AES");
+			cipher.init(Cipher.DECRYPT_MODE, key,ivSpec);
+			byte[] decordedValue = Base64.decodeBase64(encdata);
+			byte[] decdata = cipher.doFinal(decordedValue);
+			String rawdata = new String(decdata);
+
+			return rawdata;
+		} catch (NoSuchAlgorithmException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidKeyException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalBlockSizeException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (BadPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidAlgorithmParameterException e) {
+			e.printStackTrace();
+		} catch (DecoderException e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+}
diff --git a/src/main/kotlin/com/supwisdom/dlpay/security.kt b/src/main/kotlin/com/supwisdom/dlpay/security.kt
index d4166cb..8d8609f 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/security.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/security.kt
@@ -120,6 +120,7 @@
                                 UsernamePasswordAuthenticationFilter::class.java)
                         .antMatcher("/api/**")
                         .authorizeRequests()
+                        .antMatchers("/api/thirdpay/**").permitAll()
                         .antMatchers("/api/auth/**").permitAll()
                         .antMatchers("/api/thirdpart/**").permitAll()
                         .antMatchers("/api/notify/**").permitAll()
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index a606a3a..1f8d6fd 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,5 +1,5 @@
-#######################################springboot配置 start#################################
-# 单库数据库配置
+#######################################springboot\u914D\u7F6E start#################################
+# \u5355\u5E93\u6570\u636E\u5E93\u914D\u7F6E
 spring.jpa.show-sql=false
 spring.datasource.hikari.connection-timeout=60000
 spring.datasource.hikari.maximum-pool-size=5
@@ -31,8 +31,11 @@
 
 cron.offlinedtl=0/10 * * * * ?
 
-# 对账任务
+# \u5BF9\u8D26\u4EFB\u52A1
 restaurant.chkdtltask.cron=27 3/10 * * * ?
-# 统计任务
+# \u7EDF\u8BA1\u4EFB\u52A1
 restaurant.statement.cron=0 7/10 * * * ?
+# \u5361\u540C\u6B65\u4EFB\u52A1
+restaurant.sync.card.task.cron=0 0/1 * * * ?
 
+busapp.upload.transdtl.task.cron=0 0/1 * * * ?
\ No newline at end of file