大理二维码设计修改
diff --git a/bus-qrcode/build.gradle b/bus-qrcode/build.gradle
index 4132b55..aa6c290 100644
--- a/bus-qrcode/build.gradle
+++ b/bus-qrcode/build.gradle
@@ -7,7 +7,7 @@
 
 group = rootProject.group
 
-def sdkVersion = gitVersion()
+def sdkVersion = '1.0.0'
 sourceCompatibility = 1.8
 targetCompatibility = 1.8
 
@@ -15,7 +15,7 @@
     publications {
         mavenJava(MavenPublication) {
             groupId = project.group
-            artifactId = 'bus-qrcode'
+            artifactId = 'dlsmk-qrcode'
             version = sdkVersion
             from components.java
         }
@@ -37,18 +37,17 @@
 dependencies {
     implementation "org.apache.commons:commons-lang3:3.7"
     implementation 'com.eatthepath:java-otp:0.1.0'
-    implementation 'org.slf4j:slf4j-api:1.7.25'
+    implementation 'org.slf4j:slf4j-api:1.7.26'
     implementation 'commons-codec:commons-codec:1.9'
-    runtime 'org.slf4j:slf4j-parent:1.7.25'
-    runtime 'org.slf4j:slf4j-simple:1.7.25'
+    runtime 'org.slf4j:slf4j-parent:1.7.26'
     testImplementation 'junit:junit:4.12'
 }
 
 jar {
     enabled = true
-    baseName = "bus-qrcode"
+    baseName = "dlsmk-qrcode"
     manifest {
-        attributes('Bus-QRcode-Version': sdkVersion)
+        attributes('dlsmk-qrcode-version': sdkVersion)
     }
 }
 
diff --git a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/BinUtil.java b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/BinUtil.java
index b04ecd4..9b00aa0 100644
--- a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/BinUtil.java
+++ b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/BinUtil.java
@@ -58,10 +58,10 @@
   }
 
   public static String encodeBase64(byte[] data) {
-    return new Base64(true).encodeAsString(data);
+    return Base64.encodeBase64URLSafeString(data);
   }
 
   public static byte[] decodeBase64(String data) {
-    return new Base64(true).decode(data);
+    return Base64.decodeBase64(data);
   }
 }
diff --git a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/CryptoUtil.java b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/CryptoUtil.java
index 839e3c0..2998c85 100644
--- a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/CryptoUtil.java
+++ b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/CryptoUtil.java
@@ -83,7 +83,7 @@
 
   private static byte[] doAESCipher(byte[] key, byte[] iv, byte[] data, int mode) {
     try {
-      Cipher keyCipher = Cipher.getInstance("AES/CFB/NoPadding");
+      Cipher keyCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
       IvParameterSpec ivSpec = new IvParameterSpec(iv);
       SecretKeySpec secret = new SecretKeySpec(key, "AES");
       keyCipher.init(mode, secret, ivSpec);
diff --git a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/QrCode.java b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/QrCode.java
index fdbbf72..c671703 100644
--- a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/QrCode.java
+++ b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/QrCode.java
@@ -8,6 +8,8 @@
 
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.util.*;
@@ -19,15 +21,26 @@
 public class QrCode {
   private static Logger logger = LoggerFactory.getLogger(QrCode.class);
 
-  public static final String DELIMITER = ":";
-  public static final String SCOPE_DELIMITER = ",";
-  public static final String DEFAULT_SCOPE = "1"; //默认仅支持 1-大理车载消费场景
-  public static final int TOTP_OFFSET = 10; //TODO: 前后偏移10步,30*10=300s=5m 约前后5分钟内totp有效
+  public static final String FIELD_UID = "uid";
+  public static final String FIELD_CARDNO = "cardNo";
+  public static final String FIELD_CARDTYPE = "cardType";
+  private static final String FIELD_TOTP = "totp";
+  private static final String FIELD_RANDOM = "random";
+  private static final String FIELD_SIGN = "sign";
+  private static final String DELIMITER = ":";
+  public static final long timeStep = 5; //totp步长 5s
+  public static final int totpDigits = 8; //totp取的位数
 
-//  static final ArrayList<String> DEFAULT_SCOPES = new ArrayList<>();
-//  static {
-//    DEFAULT_SCOPES.add("1"); //默认仅支持 1-大理车载消费场景
-//  }
+  static final ArrayList<String> qrDataKeys = new ArrayList<>();
+
+  static {
+    qrDataKeys.add(FIELD_UID);
+    qrDataKeys.add(FIELD_CARDNO);
+    qrDataKeys.add(FIELD_CARDTYPE);
+    qrDataKeys.add(FIELD_TOTP);
+    qrDataKeys.add(FIELD_RANDOM);
+    qrDataKeys.add(FIELD_SIGN);
+  }
 
   private Builder qrBuilder;
 
@@ -36,29 +49,49 @@
   }
 
   public static class Builder {
-    private String appid;
-    private String uid;
-    private String scope;
-    private String seed = "a8aae1a41ffab089428e8e21381cc34af3b614ac7b7cba50f58470b8e50c6072";
-    private boolean resetSeed = false;
-    private byte[] iv = decodeHex("55b6f5b3287c535f8274a99354676d0b");
-    private boolean resetIv = false;
+    private String uid; //手机注册的uid 系统用户唯一ID
+    private String cardNo; //市民卡号
+    private String cardType; //公交卡类别
+    private String prefix;  //码前缀
+    private String mac;
+
     private byte[] rootKey;
-    private byte[] sKey;
-    private byte[] des3Key;
-    private String prefix;
-    private boolean debug;
+    private byte[] iv = decodeHex("55b6f5b3287c535f8274b99354676d0e");
+    private String seed = "125ea2f97689988b6501";
+    private boolean rootKeySet = false;
+    private boolean resetIv = false;
+    private boolean resetSeed = false;
+
+    private boolean debug = false;
 
     public QrCode create() {
       return new QrCode(this);
     }
 
-    public Builder appid(String appid) {
-      if (StringUtils.isBlank(appid) || !StringUtils.isAsciiPrintable(appid)) {
-        throw new RuntimeException("appid is invalid");
+    public Builder rootKey(String rootKey) {
+      if (StringUtils.isBlank(rootKey)) {
+        throw new RuntimeException("rootKey is empty");
       }
-      this.appid = appid;
-      this.des3Key = md5(appid.getBytes());
+      this.rootKey = decodeBase64(rootKey);
+      rootKeySet = true;
+      return this;
+    }
+
+    public Builder iv(String iv) {
+      if (StringUtils.isBlank(iv)) {
+        throw new RuntimeException("iv is empty");
+      }
+      this.iv = decodeHex(iv);
+      resetIv = true;
+      return this;
+    }
+
+    public Builder seed(String seed) {
+      if (StringUtils.isBlank(seed)) {
+        throw new RuntimeException("seed is empty");
+      }
+      this.seed = seed;
+      this.resetSeed = true;
       return this;
     }
 
@@ -70,45 +103,31 @@
       return this;
     }
 
-    public Builder scope(String scope) {
-      if (StringUtils.isBlank(scope)) {
-        throw new RuntimeException("scope is empty");
+    public Builder cardNo(String cardNo) {
+      if (StringUtils.isBlank(cardNo) || !StringUtils.isAsciiPrintable(cardNo)) {
+        throw new RuntimeException("cardNo is invalid");
       }
-      this.scope = scope;
+      this.cardNo = cardNo;
       return this;
     }
 
-    public Builder rootkey(String key) {
-      this.rootKey = decodeHex(key);
+    public Builder cardType(String cardType) {
+      if (StringUtils.isBlank(cardType) || !StringUtils.isAsciiPrintable(cardType)) {
+        throw new RuntimeException("cardType is invalid");
+      }
+      this.cardType = cardType;
       return this;
     }
 
-    public Builder rootkey(String appid, String skey){
-      if (StringUtils.isBlank(appid) || !StringUtils.isAsciiPrintable(appid)) {
-        throw new RuntimeException("appid is invalid");
+    public Builder card(String cardNo, String cardType) {
+      if (StringUtils.isBlank(cardNo) || !StringUtils.isAsciiPrintable(cardNo)) {
+        throw new RuntimeException("cardNo is invalid");
       }
-      if (StringUtils.isBlank(skey)) {
-        throw new RuntimeException("sKey is empty");
+      if (StringUtils.isBlank(cardType) || !StringUtils.isAsciiPrintable(cardType)) {
+        throw new RuntimeException("cardType is invalid");
       }
-      this.appid = appid;
-      this.sKey = decodeHex(skey);
-      this.des3Key = md5(appid.getBytes());
-      this.rootKey = desEncryptECB(this.des3Key, this.sKey);
-      return this;
-    }
-
-    public Builder seed(String s) {
-      if (StringUtils.isBlank(s)) {
-        throw new RuntimeException("seed is empty");
-      }
-      this.seed = s;
-      this.resetSeed = true;
-      return this;
-    }
-
-    public Builder ivHex(String v) {
-      this.iv = decodeHex(v);
-      this.resetIv = true;
+      this.cardNo = cardNo;
+      this.cardType = cardType;
       return this;
     }
 
@@ -156,32 +175,26 @@
 
   private TimeBasedOneTimePasswordGenerator totpGenerator(int passwordLength) {
     try {
-      return new TimeBasedOneTimePasswordGenerator(30, TimeUnit.SECONDS, passwordLength,
+      return new TimeBasedOneTimePasswordGenerator(timeStep, TimeUnit.SECONDS, passwordLength,
           TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA256);
     } catch (NoSuchAlgorithmException e) {
       throw new RuntimeException("TOTP Error", e);
     }
   }
 
-  private boolean checkScope(List<String> scopes) {
-    return scopes.contains(DEFAULT_SCOPE);
-  }
-
-  private boolean verifyTotp(String totp, String secret, int offset) {
-    String second = "30";
-    String seed = secret;
+  private boolean verifyTotp(String totp, String seed, int offset) {
     long T0 = 0;
     String[] keys = new String[offset * 2 + 1];
     long time = Calendar.getInstance().getTime().getTime() / 1000;
     for (int i = 0; i < keys.length; i++) {
       String steps = "0";
       try {
-        long T = (time - T0) / Long.parseLong(second);
+        long T = (time - T0) / timeStep;
         steps = Long.toHexString(T + (i - offset)).toUpperCase();
         while (steps.length() < 16) {
           steps = "0" + steps;
         }
-        String key = TOTP.generateTOTP(secret, steps, "8", TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA256);
+        String key = TOTP.generateTOTP(seed, steps, String.valueOf(totpDigits), TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA256);
         keys[i] = key;
       } catch (final Exception e) {
         System.out.println("Error : " + e);
@@ -195,87 +208,210 @@
     return false;
   }
 
-  public String qrcode() {
-    if (qrBuilder.debug) {
-      logger.info("appid=" + qrBuilder.appid);
-      logger.info("uid=" + qrBuilder.uid);
-      logger.info("scope=" + qrBuilder.scope);
-      if (qrBuilder.resetSeed) logger.info("seed=" + qrBuilder.seed);
-      if (qrBuilder.resetIv) logger.info("iv=" + encodeHex(qrBuilder.iv));
-      if (null != qrBuilder.sKey) logger.info("sKey=" + encodeHex(qrBuilder.sKey).toUpperCase());
-      if (null != qrBuilder.prefix) logger.info("prefix=" + qrBuilder.prefix);
-
-      logger.info("rootkey=" + encodeHex(qrBuilder.rootKey).toUpperCase());
-      logger.info("=======================================================");
-    }
-
-    final String randomStr = getRandomString(6); //随机数
-    final String totp = genTOTPWithSeed(qrBuilder.seed, 8);
-    final String encDataPlain = new StringJoin(DELIMITER)
-        .add(qrBuilder.uid)
-        .add(qrBuilder.scope)
-        .add(totp)
-        .add(randomStr).toString();
-
-    final byte[] encData = aesEncryptCFB(qrBuilder.rootKey, encDataPlain.getBytes(), qrBuilder.iv);
-    final String code = encodeBase64(encData);
-    if (qrBuilder.debug) {
-      logger.info("encdata plain= " + encDataPlain);
-    }
-
-    if (qrBuilder.prefix != null) {
-      return qrBuilder.prefix + code;
-    }
-    return code;
+  private byte[] getSignFactor(String data) {
+    return sha256(("{dlsmk_}" + data).getBytes());
   }
 
-  public String decodeUid(String qrcode) {
+  private byte[] byteConcat(byte[] first, byte[] second) {
+    try {
+      final ByteArrayOutputStream output = new ByteArrayOutputStream();
+      output.write(first);
+      output.write(second);
+      return output.toByteArray();
+    } catch (IOException e) {
+      throw new RuntimeException("byte concat method error!!!");
+    }
+  }
+
+  private byte[] byteConcat(byte[] first, byte[] second, byte[] third) {
+    try {
+      final ByteArrayOutputStream output = new ByteArrayOutputStream();
+      output.write(first);
+      output.write(second);
+      output.write(third);
+      return output.toByteArray();
+    } catch (IOException e) {
+      throw new RuntimeException("byte concat method error!!!");
+    }
+  }
+
+  public String qrcode() {
     if (qrBuilder.debug) {
-      logger.info("appid=" + qrBuilder.appid);
-      if (qrBuilder.resetSeed) logger.info("seed=" + qrBuilder.seed);
-      if (qrBuilder.resetIv) logger.info("iv=" + encodeHex(qrBuilder.iv));
-      if (null != qrBuilder.sKey) logger.info("sKey=" + encodeHex(qrBuilder.sKey).toUpperCase());
+      logger.info("uid=" + qrBuilder.uid);
+      logger.info("cardNo=" + qrBuilder.cardNo);
+      logger.info("cardType=" + qrBuilder.cardNo);
       if (null != qrBuilder.prefix) logger.info("prefix=" + qrBuilder.prefix);
+
+      if (qrBuilder.resetSeed) logger.info("seed=" + qrBuilder.seed);
+      if (qrBuilder.rootKeySet) logger.info("rootKey=" + encodeBase64(qrBuilder.rootKey));
+      if (qrBuilder.resetIv) logger.info("iv=" + encodeHex(qrBuilder.iv));
       logger.info("=======================================================");
     }
-    if (null == qrBuilder.rootKey) {
-      throw new RuntimeException("qr decode root key must not be null");
+
+    if (null == qrBuilder.uid || null == qrBuilder.cardNo || null == qrBuilder.cardType) {
+      throw new RuntimeException("QR code params is null");
     }
+
+    if (!qrBuilder.rootKeySet) {
+      throw new RuntimeException("QR decode root key must be set!");
+    }
+
+    final String randomStr = getRandomString(2); //随机数
+    final String totp = genTOTPWithSeed(qrBuilder.seed, totpDigits);
+    final byte[] factor = getSignFactor(qrBuilder.uid);
+    final String qrData = new StringJoin(DELIMITER)
+        .add(qrBuilder.uid)
+        .add(qrBuilder.cardNo)
+        .add(qrBuilder.cardType)
+        .add(totp)
+        .add(randomStr).toString();
+    if (qrBuilder.debug) {
+      logger.info("qrData = " + qrData);
+    }
+    final byte[] sign = sha256(byteConcat(qrData.getBytes(), factor));
+    final byte[] encDataPlain = byteConcat(qrData.getBytes(), DELIMITER.getBytes(), sign);
+    final byte[] encData = aesEncryptCFB(qrBuilder.rootKey, encDataPlain, qrBuilder.iv);
+    final String code = encodeBase64(encData);
+    String result = code;
+    if (qrBuilder.prefix != null) {
+      result = qrBuilder.prefix + code;
+    }
+    if (qrBuilder.debug) {
+      logger.info("QR code = " + result);
+    }
+    return result;
+  }
+
+  private Map<String, String> decode(String qrcode) {
+    if (qrBuilder.debug) {
+      logger.info("Start parsing QR code= "+ qrcode);
+      if (null != qrBuilder.prefix) logger.info("prefix=" + qrBuilder.prefix);
+
+      if (qrBuilder.resetSeed) logger.info("seed=" + qrBuilder.seed);
+      if (qrBuilder.rootKeySet) logger.info("rootKey=" + encodeBase64(qrBuilder.rootKey));
+      if (qrBuilder.resetIv) logger.info("iv=" + encodeHex(qrBuilder.iv));
+      logger.info("=======================================================");
+    }
+
+    if (null == qrcode) {
+      throw new RuntimeException("QR code must not be null");
+    }
+
+    if (!qrBuilder.rootKeySet) {
+      throw new RuntimeException("QR decode root key must be set!");
+    }
+
     if (qrBuilder.prefix != null) {
       qrcode = qrcode.substring(qrBuilder.prefix.length());
     }
-    final String encDataPlain = new String(aesDecryptCFB(qrBuilder.rootKey, decodeBase64(qrcode), qrBuilder.iv));
+
+    byte[] code = aesDecryptCFB(qrBuilder.rootKey, decodeBase64(qrcode), qrBuilder.iv);
+    if (null == code) {
+      throw new RuntimeException("Unable to recognize QR code!");
+    }
+    final String encData = new String(code);
     if (qrBuilder.debug) {
-      logger.info("Decode data : <" + encDataPlain + ">");
+      logger.info("QR code data: " + encData );
     }
-    String[] fields = encDataPlain.split(DELIMITER);
-    if (fields.length < 4) {
-      throw new RuntimeException("qrcode plain text format error!");
+    final String[] fields = encData.split(DELIMITER);
+    if (fields.length < 6) {
+      throw new RuntimeException("QR code plain text format error!");
     }
-    String uid = fields[0];
-    String[] scopes = fields[1].split(SCOPE_DELIMITER);
-    String totp = fields[2];
-
-    if (!verifyTotp(totp, qrBuilder.seed, TOTP_OFFSET)) {
-      throw new RuntimeException("qrcode is invalid!"); //TOTP校验
+    Map<String, String> result = new HashMap<>();
+    for (int i = 0; i < fields.length && i < qrDataKeys.size(); ++i) {
+      result.put(qrDataKeys.get(i), fields[i]);
+    }
+    final String qrData;
+    {
+      final StringJoin sj = new StringJoin(DELIMITER);
+      for (int i = 0; i < 5; ++i) {
+        sj.add(result.get(qrDataKeys.get(i)));
+      }
+      qrData = sj.toString();
+    }
+    final String uid = result.get(FIELD_UID);
+    final byte[] factor = getSignFactor(uid);
+    final byte[] calcSign = sha256(byteConcat(qrData.getBytes(), factor));
+    if (qrBuilder.debug) {
+      logger.info("calcSign=" + new String(calcSign));
+      logger.info("sign=" + result.get(FIELD_SIGN));
+    }
+    if (!new String(calcSign).equalsIgnoreCase(result.get(FIELD_SIGN))) {
+      throw new RuntimeException("QR code sign was not matched!");
     }
 
-    if (!checkScope(Arrays.asList(scopes))) {
-      throw new RuntimeException("qrcode is not supported!"); //应用场景校验
-    }
-
-    return uid;
+    return result;
   }
 
-  public String tac(String uid, Integer amount, String transdate, String transtime) {
-    if (null == qrBuilder.appid) {
-      throw new RuntimeException("appid is null");
+  /**
+   * 返回码数据不校验totp
+   * */
+  public Map<String, String> decodeWithoutTotpCheck(String qrcode) {
+    final Map<String, String> data = decode(qrcode);
+    if (null == data || data.isEmpty()) {
+      throw new RuntimeException("QR code parsing failure!");
     }
-    if (null == qrBuilder.sKey) {
-      throw new RuntimeException("sKey is null");
+
+    this.qrBuilder.mac = new StringJoin(DELIMITER)
+        .add(data.get(FIELD_UID))
+        .add(data.get(FIELD_CARDNO))
+        .add(data.get(FIELD_CARDTYPE))
+        .add(data.get(FIELD_TOTP))
+        .add(data.get(FIELD_RANDOM)).toString(); //计算流水TAC的因子
+
+    Map<String, String> result = new HashMap<>();
+    result.put(FIELD_UID, data.get(FIELD_UID));
+    result.put(FIELD_CARDNO, data.get(FIELD_CARDNO));
+    result.put(FIELD_CARDTYPE, data.get(FIELD_CARDTYPE));
+    result.put(FIELD_TOTP, data.get(FIELD_TOTP)); //不校验totp,直接返回
+    return result;
+  }
+
+  /**
+   * 返回码数据校验totp
+   * */
+  public Map<String, String> decodeWithTotpCheck(String qrcode) {
+    return decodeWithTotpCheck(qrcode, 3); //默认15s失效
+  }
+
+  /**
+   * 返回码数据校验totp
+   * */
+  public Map<String, String> decodeWithTotpCheck(String qrcode, int offset) {
+    final Map<String, String> data = decode(qrcode);
+    if (null == data || data.isEmpty()) {
+      throw new RuntimeException("QR code parsing failure!");
     }
-    final String encdata = uid + qrBuilder.appid + amount + transdate + transtime + "{" + encodeHex(qrBuilder.sKey).toUpperCase() + "}";
-    return encodeHex(sha256(encdata.getBytes())).toUpperCase();
+
+    //校验totp
+    if (!verifyTotp(data.get(FIELD_TOTP), this.qrBuilder.seed, offset)) {
+      throw new RuntimeException("qrcode is invalid!"); //二维码已无效
+    }
+
+    this.qrBuilder.mac = new StringJoin(DELIMITER)
+        .add(data.get(FIELD_UID))
+        .add(data.get(FIELD_CARDNO))
+        .add(data.get(FIELD_CARDTYPE))
+        .add(data.get(FIELD_TOTP))
+        .add(data.get(FIELD_RANDOM)).toString(); //计算流水TAC的因子
+
+    Map<String, String> result = new HashMap<>();
+    result.put(FIELD_UID, data.get(FIELD_UID));
+    result.put(FIELD_CARDNO, data.get(FIELD_CARDNO));
+    result.put(FIELD_CARDTYPE, data.get(FIELD_CARDTYPE));
+    return result;
+  }
+
+  public String tac(Integer amount, String transDate, String transTime) {
+    if (null == this.qrBuilder.mac) {
+      throw new RuntimeException("Please decode QR code first!");
+    }
+    if (this.qrBuilder.debug) {
+      logger.info("mac = " + this.qrBuilder.mac);
+    }
+
+    final String encData = this.qrBuilder.mac + amount + transDate + transTime + "{dlsmk}";
+    return encodeBase64(sha256(encData.getBytes())).toLowerCase();
   }
 
 }
diff --git a/bus-qrcode/src/test/java/com/supwisdom/dlpay/busqrcode/QrcodeTest.java b/bus-qrcode/src/test/java/com/supwisdom/dlpay/busqrcode/QrcodeTest.java
index 9e8bae1..f67a828 100644
--- a/bus-qrcode/src/test/java/com/supwisdom/dlpay/busqrcode/QrcodeTest.java
+++ b/bus-qrcode/src/test/java/com/supwisdom/dlpay/busqrcode/QrcodeTest.java
@@ -1,61 +1,37 @@
 package com.supwisdom.dlpay.busqrcode;
 
 import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import static com.supwisdom.dlpay.busqrcode.BinUtil.*;
-import static com.supwisdom.dlpay.busqrcode.CryptoUtil.*;
-import static org.junit.Assert.*;
+import java.util.Map;
 
 public class QrcodeTest {
-    private Logger logger = LoggerFactory.getLogger(QrcodeTest.class);
+  @Test
+  public void testBusQrcode() {
+    final String uid = "0a5de6ce985d43989b7ebe64ad8eb9c3";
+    final String cardNo = "00001252";
+    final String cardtype = "80";
+    final String rootKeyB64 = "Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU=";
+    final String ivHex = "55b6f5b3287c535f8274b99354676d0e";
 
-    @Test
-    public void testBusQrcode() {
-        String prefix = "dlsmk_";
-        String appid = "10001";
-        String uid = "ff8080816cf13e79016cf15d63f40011";
-        String scope = "1,2";
-        String rkey = "0A5DE6CE985D43989B7EBE64AD8EB9C3";
-        Integer amount = 100;
-        String transdate = "20200220";
-        String transtime = "090921";
+    try {
+      QrCode handler = QrCode.builder()
+          .rootKey(rootKeyB64)
+          .iv(ivHex)
+          .uid(uid)
+          .card(cardNo, cardtype)
+          .debug(true)
+          .create();
+      final String qrcode = handler.qrcode();
 
-        String skey = encodeHex(desDecryptECB(md5(appid.getBytes()), decodeHex(rkey))).toUpperCase();
-        logger.info("skey=[" + skey + "]");
-        String rootKey = encodeHex(desEncryptECB(md5(appid.getBytes()), decodeHex(skey))).toUpperCase();
-        assertEquals("rootkey与skey互为3des加密关系错误!", rkey, rootKey);
+      QrCode decHandler = QrCode.builder()
+          .rootKey(rootKeyB64)
+          .iv(ivHex)
+          .debug(true).create();
+      Map<String, String> result = decHandler.decodeWithTotpCheck(qrcode);
+      String tac = decHandler.tac(1000, "20201028", "144521");
 
-        QrCode encQr = QrCode.builder()
-                .appid(appid)
-                .rootkey(rkey)
-                .uid(uid)
-                .scope(scope)
-                .prefix(prefix)
-                .debug(true)
-                .create();
-        String qrcode = encQr.qrcode();
-        logger.info("qrcode=[" + qrcode + "]");
-
-        QrCode decQr = QrCode.builder()
-                .rootkey(appid, skey)
-                .prefix(prefix)
-                .debug(true)
-                .create();
-
-//        try {
-//            Thread.sleep(6*60*1000); //totp测试
-//        } catch (InterruptedException e) {
-//            e.printStackTrace();
-//        }
-
-        String decUid = decQr.decodeUid(qrcode);
-        assertEquals("解码uid错误!", uid, decUid);
-
-        String tac = decQr.tac(uid, amount, transdate, transtime);
-        String tacData = uid + appid + amount + transdate + transtime + "{" + skey + "}";
-        String testTac = encodeHex(sha256(tacData.getBytes())).toUpperCase();
-        assertEquals("tac计算错误", tac, testTac);
+    } catch (Exception e) {
+      e.printStackTrace();
     }
+  }
 }
\ No newline at end of file
diff --git a/payapi/build.gradle b/payapi/build.gradle
index 5d8338c..f10aab8 100644
--- a/payapi/build.gradle
+++ b/payapi/build.gradle
@@ -57,6 +57,16 @@
 
 docker.dependsOn(jar)
 
+repositories {
+    maven {
+        url "http://ykt-nx.supwisdom.com/repository/ecard-repo/"
+        credentials {
+            username 'ecard'
+            password 'Ecard4SUP'
+        }
+    }
+}
+
 dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
     implementation 'org.springframework.boot:spring-boot-starter-data-redis'
@@ -114,6 +124,9 @@
     /*支付宝SDK*/
     implementation group: 'com.alipay.sdk', name: 'alipay-sdk-java', version: '3.7.110.ALL'
 
+    /*大理二维码jar*/
+    implementation 'com.supwisdom:dlsmk-qrcode:1.0.0'
+
     annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
     annotationProcessor 'org.projectlombok:lombok:1.18.8'
     compileOnly 'org.projectlombok:lombok:1.18.8'
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..f8c4e5b 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,7 @@
 
     @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?
 }
\ 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 79dbbb9..a94e50b 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
 
@@ -124,8 +127,8 @@
         query.setParameter("endtime", enddate)
         query.unwrap(NativeQueryImpl::class.java).setResultTransformer(Transformers.aliasToBean(CitizenCardInfo::class.java))
         val list = query.resultList as List<CitizenCardInfo>
-        resp.retcode=0
-        resp.retmsg="OK"
+        resp.retcode = 0
+        resp.retmsg = "OK"
         resp.cards = list
         return resp
     }
@@ -172,7 +175,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
@@ -223,4 +226,12 @@
             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)
+        }
+    }
 }
\ 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..cbdeaef 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
@@ -7,6 +7,7 @@
 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.TradeDict
@@ -64,33 +65,66 @@
             resp.retmsg = "银行卡未签约"
             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
+        }
 
-        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))
+        val handle = QrCode.builder()
+                .rootKey(rootkey)
+                .iv(iv)
+                .uid(uid)
+                .card(cityCard.cardno, cityCard.busCardType ?: "80")
+                .create()
+        val qrcode = handle.qrcode()
         resp.retcode = 0
-        resp.retmsg = key
+        resp.retmsg = qrcode
         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 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()) {
+        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