From: kaixiang.xia Date: Thu, 5 Nov 2020 06:57:08 +0000 (+0800) Subject: 新版二维码相关修改 X-Git-Tag: 1.0.29^2~11 X-Git-Url: https://source.supwisdom.com/gerrit/gitweb?a=commitdiff_plain;h=427418fb634f2ab6d5eca177fa121a4dbe6b1131;p=epayment%2Ffood_payapi.git 新版二维码相关修改 --- diff --git a/bus-qrcode/build.gradle b/bus-qrcode/build.gradle index aa6c2900..55a94ee1 100644 --- a/bus-qrcode/build.gradle +++ b/bus-qrcode/build.gradle @@ -7,7 +7,7 @@ plugins { group = rootProject.group -def sdkVersion = '1.0.0' +def sdkVersion = '1.3.0' sourceCompatibility = 1.8 targetCompatibility = 1.8 @@ -36,9 +36,11 @@ publishing { dependencies { implementation "org.apache.commons:commons-lang3:3.7" - implementation 'com.eatthepath:java-otp:0.1.0' + implementation 'com.eatthepath:java-otp:0.2.0' implementation 'org.slf4j:slf4j-api:1.7.26' implementation 'commons-codec:commons-codec:1.9' + implementation 'org.bouncycastle:bcprov-jdk15to18:1.66' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.66' runtime 'org.slf4j:slf4j-parent:1.7.26' testImplementation 'junit:junit:4.12' } 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 2998c854..e3a98b8b 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 @@ -1,20 +1,23 @@ package com.supwisdom.dlpay.busqrcode; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + import javax.crypto.*; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.DESedeKeySpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Arrays; public class CryptoUtil { + static { + Security.addProvider(new BouncyCastleProvider()); + } + private static byte[] doCipher(Cipher cipher, SecretKey key, int mode, byte[] data) { try { cipher.init(mode, key); @@ -83,12 +86,12 @@ public class CryptoUtil { private static byte[] doAESCipher(byte[] key, byte[] iv, byte[] data, int mode) { try { - Cipher keyCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + Cipher keyCipher = Cipher.getInstance("AES/CBC/PKCS7Padding", BouncyCastleProvider.PROVIDER_NAME); IvParameterSpec ivSpec = new IvParameterSpec(iv); SecretKeySpec secret = new SecretKeySpec(key, "AES"); keyCipher.init(mode, secret, ivSpec); return keyCipher.doFinal(data); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | NoSuchProviderException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } 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 c6717035..6e5fd891 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 @@ -3,8 +3,6 @@ package com.supwisdom.dlpay.busqrcode; import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -12,6 +10,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; import java.util.*; import java.util.concurrent.TimeUnit; @@ -19,8 +19,6 @@ import static com.supwisdom.dlpay.busqrcode.BinUtil.*; import static com.supwisdom.dlpay.busqrcode.CryptoUtil.*; public class QrCode { - private static Logger logger = LoggerFactory.getLogger(QrCode.class); - public static final String FIELD_UID = "uid"; public static final String FIELD_CARDNO = "cardNo"; public static final String FIELD_CARDTYPE = "cardType"; @@ -163,10 +161,9 @@ public class QrCode { private String genTOTPWithSeed(String seed, int pl) { final TimeBasedOneTimePasswordGenerator alg = totpGenerator(pl); final SecretKey secretKey = new SecretKeySpec(decodeHex(seed), - TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA256); + TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA1); try { - Date now = Calendar.getInstance().getTime(); - return StringUtils.leftPad(String.format("%d", alg.generateOneTimePassword(secretKey, now)), + return StringUtils.leftPad(String.format("%d", alg.generateOneTimePassword(secretKey, Instant.now())), pl, '0'); } catch (InvalidKeyException e) { throw new RuntimeException("TOTP Error", e); @@ -175,8 +172,8 @@ public class QrCode { private TimeBasedOneTimePasswordGenerator totpGenerator(int passwordLength) { try { - return new TimeBasedOneTimePasswordGenerator(timeStep, TimeUnit.SECONDS, passwordLength, - TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA256); + return new TimeBasedOneTimePasswordGenerator(Duration.ofSeconds(timeStep), passwordLength, + TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA1); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("TOTP Error", e); } @@ -185,7 +182,7 @@ public class QrCode { 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; + long time = Instant.now().toEpochMilli() / 1000; for (int i = 0; i < keys.length; i++) { String steps = "0"; try { @@ -194,7 +191,7 @@ public class QrCode { while (steps.length() < 16) { steps = "0" + steps; } - String key = TOTP.generateTOTP(seed, steps, String.valueOf(totpDigits), TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA256); + String key = TOTP.generateTOTP(seed, steps, String.valueOf(totpDigits), TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA1); keys[i] = key; } catch (final Exception e) { System.out.println("Error : " + e); @@ -237,27 +234,31 @@ public class QrCode { public String qrcode() { if (qrBuilder.debug) { - logger.info("uid=" + qrBuilder.uid); - logger.info("cardNo=" + qrBuilder.cardNo); - logger.info("cardType=" + qrBuilder.cardNo); - if (null != qrBuilder.prefix) logger.info("prefix=" + qrBuilder.prefix); + System.out.println("====================== Start Build QR Code ================================="); + System.out.println("uid=[" + qrBuilder.uid + "]"); + System.out.println("cardNo=[" + qrBuilder.cardNo + "]"); + System.out.println("cardType=[" + qrBuilder.cardType + "]"); + if (null != qrBuilder.prefix) System.out.println("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 (qrBuilder.resetSeed) System.out.println("seed=[" + qrBuilder.seed + "]"); + if (qrBuilder.rootKeySet) System.out.println("rootKey=[" + encodeBase64(qrBuilder.rootKey) + "]"); + if (qrBuilder.resetIv) System.out.println("iv=[" + encodeHex(qrBuilder.iv) + "]"); + System.out.println("*****************************************************************************"); } if (null == qrBuilder.uid || null == qrBuilder.cardNo || null == qrBuilder.cardType) { - throw new RuntimeException("QR code params is null"); + throw new RuntimeException("二维码必要参数为空!"); } if (!qrBuilder.rootKeySet) { - throw new RuntimeException("QR decode root key must be set!"); + throw new RuntimeException("二维码rootKey必须设置!"); } - final String randomStr = getRandomString(2); //随机数 + if (qrBuilder.debug) { + System.out.println("totp time=[" + Instant.now().toEpochMilli() / 1000 + "]"); + } final String totp = genTOTPWithSeed(qrBuilder.seed, totpDigits); + final String randomStr = getRandomString(2); //随机数 final byte[] factor = getSignFactor(qrBuilder.uid); final String qrData = new StringJoin(DELIMITER) .add(qrBuilder.uid) @@ -266,8 +267,9 @@ public class QrCode { .add(totp) .add(randomStr).toString(); if (qrBuilder.debug) { - logger.info("qrData = " + qrData); + System.out.println("QR Data=[" + 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); @@ -277,28 +279,31 @@ public class QrCode { result = qrBuilder.prefix + code; } if (qrBuilder.debug) { - logger.info("QR code = " + result); + System.out.println("hex sign=[" + encodeHex(sign) + "]"); + System.out.println("hex encDataPlain=[" + encodeHex(encData) + "]"); + System.out.println("QR code=[" + result + "]"); + System.out.println("====================== Build QR Code End ================================="); } return result; } private Map decode(String qrcode) { if (qrBuilder.debug) { - logger.info("Start parsing QR code= "+ qrcode); - if (null != qrBuilder.prefix) logger.info("prefix=" + qrBuilder.prefix); + System.out.println("====================== Start Decode QR Code ================================="); + if (null != qrBuilder.prefix) System.out.println("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 (qrBuilder.resetSeed) System.out.println("seed=[" + qrBuilder.seed + "]"); + if (qrBuilder.rootKeySet) System.out.println("rootKey=[" + encodeBase64(qrBuilder.rootKey) + "]"); + if (qrBuilder.resetIv) System.out.println("iv=[" + encodeHex(qrBuilder.iv) + "]"); + System.out.println("*****************************************************************************"); } if (null == qrcode) { - throw new RuntimeException("QR code must not be null"); + throw new RuntimeException("请传递二维码内容!"); } if (!qrBuilder.rootKeySet) { - throw new RuntimeException("QR decode root key must be set!"); + throw new RuntimeException("二维码rootKey必须设置!"); } if (qrBuilder.prefix != null) { @@ -307,15 +312,15 @@ public class QrCode { byte[] code = aesDecryptCFB(qrBuilder.rootKey, decodeBase64(qrcode), qrBuilder.iv); if (null == code) { - throw new RuntimeException("Unable to recognize QR code!"); + throw new RuntimeException("无法识别二维码!"); } final String encData = new String(code); if (qrBuilder.debug) { - logger.info("QR code data: " + encData ); + System.out.println("encDataPlain=[" + encData + "]"); } - final String[] fields = encData.split(DELIMITER); + final String[] fields = encData.split(DELIMITER, 6); if (fields.length < 6) { - throw new RuntimeException("QR code plain text format error!"); + throw new RuntimeException("二维码数据异常!"); } Map result = new HashMap<>(); for (int i = 0; i < fields.length && i < qrDataKeys.size(); ++i) { @@ -333,11 +338,12 @@ public class QrCode { 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)); + System.out.println("calcSign=[" + new String(calcSign) + "]"); + System.out.println(" sign=[" + result.get(FIELD_SIGN) + "]"); + System.out.println("====================== Decode QR Code End ================================="); } if (!new String(calcSign).equalsIgnoreCase(result.get(FIELD_SIGN))) { - throw new RuntimeException("QR code sign was not matched!"); + throw new RuntimeException("二维码验证错误!"); } return result; @@ -345,11 +351,11 @@ public class QrCode { /** * 返回码数据不校验totp - * */ + */ public Map decodeWithoutTotpCheck(String qrcode) { final Map data = decode(qrcode); if (null == data || data.isEmpty()) { - throw new RuntimeException("QR code parsing failure!"); + throw new RuntimeException("二维码解析失败!"); } this.qrBuilder.mac = new StringJoin(DELIMITER) @@ -369,23 +375,23 @@ public class QrCode { /** * 返回码数据校验totp - * */ + */ public Map decodeWithTotpCheck(String qrcode) { return decodeWithTotpCheck(qrcode, 3); //默认15s失效 } /** * 返回码数据校验totp - * */ + */ public Map decodeWithTotpCheck(String qrcode, int offset) { final Map data = decode(qrcode); if (null == data || data.isEmpty()) { - throw new RuntimeException("QR code parsing failure!"); + throw new RuntimeException("二维码解析失败!"); } //校验totp if (!verifyTotp(data.get(FIELD_TOTP), this.qrBuilder.seed, offset)) { - throw new RuntimeException("qrcode is invalid!"); //二维码已无效 + throw new RuntimeException("二维码已失效!"); //二维码已无效 } this.qrBuilder.mac = new StringJoin(DELIMITER) @@ -404,14 +410,18 @@ public class QrCode { public String tac(Integer amount, String transDate, String transTime) { if (null == this.qrBuilder.mac) { - throw new RuntimeException("Please decode QR code first!"); + throw new RuntimeException("请先解析二维码!"); } if (this.qrBuilder.debug) { - logger.info("mac = " + this.qrBuilder.mac); + System.out.println("mac=[" + this.qrBuilder.mac + "]"); } final String encData = this.qrBuilder.mac + amount + transDate + transTime + "{dlsmk}"; - return encodeBase64(sha256(encData.getBytes())).toLowerCase(); + final String tac = encodeBase64(sha256(encData.getBytes())); + if (this.qrBuilder.debug) { + System.out.println("tac=[" + tac + "]"); + } + return tac; } } 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 f67a8282..fc3b3a09 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 @@ -10,8 +10,9 @@ public class QrcodeTest { final String uid = "0a5de6ce985d43989b7ebe64ad8eb9c3"; final String cardNo = "00001252"; final String cardtype = "80"; - final String rootKeyB64 = "Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU="; + final String rootKeyB64 = "wDp3/3NPEi+R0peokVv010GkDk1mRTp3tUB/lCEVRAA="; final String ivHex = "55b6f5b3287c535f8274b99354676d0e"; + final String qr="Szgp1QkGFy0DJ6mPVg4wMuOE5XuSe7eZrocdPDAs_iroqlRsDfitBrL5LFl3TwWhehBkE-5JTh1dbOydgxhDVdHrDjsdnX5Ydi4GcxACUMBXnpm3GaRZW66fD6A7rjCW"; try { QrCode handler = QrCode.builder() @@ -29,6 +30,7 @@ public class QrcodeTest { .debug(true).create(); Map result = decHandler.decodeWithTotpCheck(qrcode); String tac = decHandler.tac(1000, "20201028", "144521"); + System.out.println("tac="+tac); } catch (Exception e) { e.printStackTrace(); diff --git a/config/application-devel-pg.properties b/config/application-devel-pg.properties index 06713bbe..371f88b2 100644 --- a/config/application-devel-pg.properties +++ b/config/application-devel-pg.properties @@ -3,6 +3,8 @@ spring.main.banner-mode=off spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false +spring.jpa.show-sql=false +logging.level.org.hibernate.SQL=error # Postgresql settings spring.datasource.platform=postgresql #spring.datasource.url=jdbc:postgresql://ykt.supwisdom.com:15432/payapidev @@ -28,31 +30,38 @@ security.request.sign=false ################################################## ## quartz task scheduler shopbalance.updater.cron=- -##################多租户配置 end################################ +dayend.settletask.cron=- +query.third.transdtl.result.cron=- +payapi.sourcetype.checker.scheduler=- +citizencard.dolosstask.cron=- +send.delay.notice.task.cron=29 0/1 * * * ? +points.outdate.cron=0 0 0 * * ? +points.consume.cron=0 0 0 * * ? +##################\u591A\u79DF\u6237\u914D\u7F6E end################################ ############################################# spring.cloud.consul.enabled=false spring.cloud.consul.host=172.28.201.70 spring.cloud.consul.port=8500 #============== kafka =================== -# 指定kafka 代理地址,可以多个 +# \u6307\u5B9Akafka \u4EE3\u7406\u5730\u5740\uFF0C\u53EF\u4EE5\u591A\u4E2A spring.kafka.bootstrap-servers=172.28.201.101:9192 #=============== provider ======================= spring.kafka.producer.retries=3 -# 每次批量发送消息的数量 +# \u6BCF\u6B21\u6279\u91CF\u53D1\u9001\u6D88\u606F\u7684\u6570\u91CF spring.kafka.producer.batch-size=16384 spring.kafka.producer.buffer-memory=33554432 -# 指定消息key和消息体的编解码方式 +# \u6307\u5B9A\u6D88\u606Fkey\u548C\u6D88\u606F\u4F53\u7684\u7F16\u89E3\u7801\u65B9\u5F0F spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer #===============kafka consumer ======================= -# 指定默认消费者group id +# \u6307\u5B9A\u9ED8\u8BA4\u6D88\u8D39\u8005group id spring.kafka.listen.auto.start=false spring.kafka.consumer.group-id=epaymessager1 spring.kafka.consumer.auto-offset-reset=earliest spring.kafka.consumer.enable-auto-commit=true spring.kafka.consumer.auto-commit-interval=100 -# 指定消息key和消息体的编解码方式 +# \u6307\u5B9A\u6D88\u606Fkey\u548C\u6D88\u606F\u4F53\u7684\u7F16\u89E3\u7801\u65B9\u5F0F spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodeKey.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodeKey.java new file mode 100644 index 00000000..d40cbc99 --- /dev/null +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodeKey.java @@ -0,0 +1,18 @@ +package com.supwisdom.dlpay.api.bean; + +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class CitizenQrcodeKey extends ApiResponse { + private String rootKey; + private String iv; + private String prefix; //二维码前缀 + private long offsetSec = 0; //系统时间 - 请求的时间。默认系统时钟无偏差 + private int totpStep = 5; //计算totp的step 默认5s算一个totp + private int totpOffset = 3; //校验totp的偏移量 默认前后5*3=15s二维码有效 + +} diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodeKeyParam.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodeKeyParam.java new file mode 100644 index 00000000..004aa07a --- /dev/null +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodeKeyParam.java @@ -0,0 +1,21 @@ +package com.supwisdom.dlpay.api.bean; + +import com.supwisdom.dlpay.api.APIRequestParam; +import com.supwisdom.dlpay.api.annotation.Sign; +import com.supwisdom.dlpay.api.exception.RequestParamCheckException; +import com.supwisdom.dlpay.api.util.DateUtil; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CitizenQrcodeKeyParam extends APIRequestParam { + @Sign + private String timestamp; + + @Override + public boolean checkParam() throws RequestParamCheckException { + if (!DateUtil.checkDatetimeValid(timestamp, DateUtil.DATETIME_FMT)) throw new RequestParamCheckException("时间戳格式错误[yyyyMMddHHmmss]"); + return true; + } +} diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodePayinitParam.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodePayinitParam.java new file mode 100644 index 00000000..08d67891 --- /dev/null +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/CitizenQrcodePayinitParam.java @@ -0,0 +1,71 @@ +package com.supwisdom.dlpay.api.bean; + +import com.supwisdom.dlpay.api.APIRequestParam; +import com.supwisdom.dlpay.api.annotation.Sign; +import com.supwisdom.dlpay.api.exception.RequestParamCheckException; +import com.supwisdom.dlpay.api.util.Constants; +import com.supwisdom.dlpay.api.util.DateUtil; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import java.util.Arrays; + +@Getter +@Setter +public class CitizenQrcodePayinitParam extends APIRequestParam { + @Sign + @NotNull(message = "支付码不能为空") + private String qrcode; + + @Sign + @NotNull(message = "请指定支付码是联机还是脱机") + private String qrcodeType; //online 或 offline + + @Sign + @NotNull(message = "请指定交易商户") + private String shopaccno; + + @Sign + @NotNull(message = "交易金额不能为空") + @Positive(message = "交易金额必须大于零") + private Integer amount; + + @Sign + @NotNull(message = "对接系统唯一订单号不能为空") + private String billno; + + @Sign + @NotNull(message = "交易日期不能为空") + private String transdate; + + @Sign + @NotNull(message = "交易时间不能为空") + private String transtime; + + @Sign + @NotNull(message = "流水类型不能为空") + private String dtltype; + + @Sign + private String cardNo; //市民卡号 + + @Sign + private String tac; //脱机消费时要传流水的tac + + + @Override + public boolean checkParam() throws RequestParamCheckException { + if (!DateUtil.checkDatetimeValid(transdate, DateUtil.DATE_FMT)) + throw new RequestParamCheckException("交易日期错误[yyyyMMdd]"); + if (!DateUtil.checkDatetimeValid(transtime, DateUtil.TIME_FMT)) + throw new RequestParamCheckException("交易时间错误[HHmmss]"); + if (!Arrays.asList(Constants.STATE_ONLINE, Constants.STATE_OFFLINE).contains(qrcodeType)) + throw new RequestParamCheckException("请指定支付码是联机还是脱机"); + if (Constants.STATE_OFFLINE.equals(qrcodeType) && StringUtils.isEmpty(tac)) + throw new RequestParamCheckException("脱机码时流水TAC不能为空"); + return true; + } +} diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/QrcodeParam.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/QrcodeParam.java index cca72786..044235d1 100644 --- a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/QrcodeParam.java +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/QrcodeParam.java @@ -18,10 +18,10 @@ public class QrcodeParam extends APIRequestParam { @NotEmpty(message = "手机用户id不能为空") private String uid; @Sign - @NotEmpty(message = "secertkey不能为空") +// @NotEmpty(message = "secertkey不能为空") private String secertkey; @Sign - @NotEmpty(message = "rsapublic不能为空") +// @NotEmpty(message = "rsapublic不能为空") private String rsapublic; @Override diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/util/Constants.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/util/Constants.java index 3c4aebe3..17484d48 100644 --- a/payapi-common/src/main/java/com/supwisdom/dlpay/api/util/Constants.java +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/util/Constants.java @@ -4,4 +4,8 @@ public class Constants { public static final String SEX_MALE = "male"; public static final String SEX_FEMALE = "female"; + public static final String STATE_ONLINE = "online"; + public static final String STATE_OFFLINE = "offline"; + + } diff --git a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/CitizenCardPayProxy.java b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/CitizenCardPayProxy.java index d68a2385..d4c42b72 100644 --- a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/CitizenCardPayProxy.java +++ b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/CitizenCardPayProxy.java @@ -1,9 +1,8 @@ package com.supwisdom.dlpay.paysdk.proxy; -import com.supwisdom.dlpay.api.bean.CitizenCardPayfinishParam; -import com.supwisdom.dlpay.api.bean.CitizenCardPayinitParam; -import com.supwisdom.dlpay.api.bean.CitizenPayResponse; +import com.supwisdom.dlpay.api.bean.*; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -17,4 +16,11 @@ public interface CitizenCardPayProxy { @RequestMapping(value = "/api/consume/citizencard/payfinish", method = RequestMethod.GET) CitizenPayResponse citizencardPayFinish(@RequestBody CitizenCardPayfinishParam param); + + @PostMapping("/api/consume/citizencard/qrcodekey") + CitizenQrcodeKey queryQrcodeKey(@RequestBody CitizenQrcodeKeyParam param); + + @PostMapping("/api/consume/citizencard/qrcodepayinit") + CitizenPayResponse citizencardQrcodePayinit(@RequestBody CitizenQrcodePayinitParam param); + } diff --git a/payapi/build.gradle b/payapi/build.gradle index f10aab85..1462f81c 100644 --- a/payapi/build.gradle +++ b/payapi/build.gradle @@ -119,13 +119,13 @@ dependencies { implementation 'log4j:log4j:1.2.17' implementation 'com.alibaba:fastjson:1.2.60' - implementation 'com.eatthepath:java-otp:0.1.0' + implementation 'com.eatthepath:java-otp:0.2.0' implementation project(':payapi-common') /*支付宝SDK*/ implementation group: 'com.alipay.sdk', name: 'alipay-sdk-java', version: '3.7.110.ALL' /*大理二维码jar*/ - implementation 'com.supwisdom:dlsmk-qrcode:1.0.0' + implementation 'com.supwisdom:dlsmk-qrcode:1.3.0' annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" annotationProcessor 'org.projectlombok:lombok:1.18.8' diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/UserSecretDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/UserSecretDao.java index a31419d3..22c57576 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/UserSecretDao.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/UserSecretDao.java @@ -2,6 +2,9 @@ package com.supwisdom.dlpay.api.dao; import com.supwisdom.dlpay.api.domain.TUserSecret; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface UserSecretDao extends JpaRepository { + @Query("from TUserSecret a where a.uid=?1 ") + TUserSecret getByUid(String uid); } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt index 87e054cd..692be0c0 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt @@ -11,6 +11,7 @@ import com.supwisdom.dlpay.api.bean.groups.ConfirmAction import com.supwisdom.dlpay.api.bean.groups.InitAction import com.supwisdom.dlpay.api.domain.TSourceType import com.supwisdom.dlpay.api.service.* +import com.supwisdom.dlpay.api.util.Constants import com.supwisdom.dlpay.exception.TransactionCheckException import com.supwisdom.dlpay.framework.ResponseBodyBuilder import com.supwisdom.dlpay.framework.service.SystemUtilService @@ -780,12 +781,29 @@ class ConsumeAPIController { * */ @PostMapping("/qrcodequery") fun qrcodeQuery(@RequestBody param: DoorQRCodeParam): ResponseEntity { - val token = redisTemplate.opsForValue().get(param.qrcode) - if (token.isNullOrEmpty()) { - return ResponseEntity.ok(ResponseBodyBuilder.create() - .fail(1, "二维码不存在或已失效")) +// val token = redisTemplate.opsForValue().get(param.qrcode) +// if (token.isNullOrEmpty()) { +// return ResponseEntity.ok(ResponseBodyBuilder.create() +// .fail(1, "二维码不存在或已失效")) +// } + val ret = qrCodeService.decodeCode(param.qrcode) + return if (ret.retcode == 0) { + ResponseEntity.ok(ResponseBodyBuilder.create() + .success(ret, "成功")) + } else { + ResponseEntity.ok(ResponseBodyBuilder.create() + .fail(ret.retcode, ret.retmsg)) } - val ret = qrCodeService.decodeCode(token) + } + + /** + * ============================================================================ + * 获取市民卡二维码key + * ============================================================================ + * */ + @PostMapping("/citizencard/qrcodekey") + fun queryQrcodeKey(@RequestBody param: CitizenQrcodeKeyParam): ResponseEntity { + val ret = qrCodeService.queryQRCodeKeys(param.timestamp) return if (ret.retcode == 0) { ResponseEntity.ok(ResponseBodyBuilder.create() .success(ret, "成功")) @@ -794,4 +812,31 @@ class ConsumeAPIController { .fail(ret.retcode, ret.retmsg)) } } + + /** + * ============================================================================ + * 市民卡二维码交易初始化 + * ============================================================================ + * */ + @PostMapping("/citizencard/qrcodepayinit") + fun citizencardQrcodePayinit(@Valid @RequestBody param: CitizenQrcodePayinitParam): ResponseEntity { + val pInfo = qrCodeService.decodeCode(param.qrcode, "online" == param.qrcodeType, param.cardNo, param.tac, param.amount, param.transdate, param.transtime) + if (pInfo.retcode != 0) { + //二维码解析错误 + return ResponseEntity.ok(ResponseBodyBuilder.create().fail(pInfo.retcode, pInfo.retmsg)) + } + + //直接走 市民卡[交易初始化] 接口 + return citizencardPayinit(CitizenCardPayinitParam().apply { + cardNo = pInfo.citycardno + shopaccno = param.shopaccno + amount = param.amount + billno = param.billno + transdate = param.transdate + transtime = param.transtime + dtltype = param.dtltype + }) + } + + } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/card_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/card_service.kt index f8c4e5be..5c60767b 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 @@ -36,4 +36,6 @@ interface CardService { @Transactional(rollbackFor = arrayOf(Exception::class), readOnly = true) fun findCardByUseridAndCardphyid(userid: String, cardtype: String, cardphyid: String?): TCard? + + fun findCardByCardnoAndCardtype(cardno: String, cardtype: String): TCard? } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/card_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/card_service_impl.kt index a94e50b9..1985c94e 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 @@ -234,4 +234,8 @@ class CardServiceImpl : CardService { cardDao.findBankcardByCitizencard(userid, cardtype, cardphyid) } } + + override fun findCardByCardnoAndCardtype(cardno: String, cardtype: String): TCard? { + return cardDao.findCardByCardnoAndCardtype(cardno, cardtype) + } } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt index cbdeaefd..5560a5d3 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 @@ -1,7 +1,7 @@ package com.supwisdom.dlpay.api.service.impl -import com.google.gson.Gson import com.supwisdom.dlpay.api.bean.ApiResponse +import com.supwisdom.dlpay.api.bean.CitizenQrcodeKey import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse import com.supwisdom.dlpay.api.bean.QrcodeParam import com.supwisdom.dlpay.api.service.CardService @@ -9,16 +9,16 @@ import com.supwisdom.dlpay.api.service.QRCodeService import com.supwisdom.dlpay.api.service.UserService import com.supwisdom.dlpay.busqrcode.QrCode import com.supwisdom.dlpay.framework.service.SystemUtilService -import com.supwisdom.dlpay.framework.util.MD5 +import com.supwisdom.dlpay.framework.util.DateUtil +import com.supwisdom.dlpay.framework.util.StringUtil import com.supwisdom.dlpay.framework.util.TradeDict import com.supwisdom.dlpay.mobile.service.MobileApiService -import com.supwisdom.dlpay.util.* +import com.supwisdom.dlpay.util.ConstantUtil import mu.KotlinLogging import org.apache.commons.lang.math.NumberUtils import org.springframework.beans.factory.annotation.Autowired import org.springframework.data.redis.core.RedisTemplate import org.springframework.stereotype.Service -import java.time.Duration @Service class QRCodeServiceImpl:QRCodeService{ @@ -35,11 +35,31 @@ class QRCodeServiceImpl:QRCodeService{ val logger = KotlinLogging.logger { } - override fun encodeCode(uid:String): ApiResponse { + private fun qrcodeConfig(): Map { + var step = "5" + var offset = "3" + var debug = "false" + val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey")?.trim() ?: "" + val iv = systemUtilService.getBusinessValue("aes.cfb.iv")?.trim() ?: "" + val prefix = systemUtilService.getBusinessValue("dlsmk.qrcode.prefix")?.trim() ?: "" + val totpStep = systemUtilService.getBusinessValue("dlsmk.qrcode.totp.step") + val totpOffset = systemUtilService.getBusinessValue("dlsmk.qrcode.totp.offset") + val debugEnable = systemUtilService.getBusinessValue("dlsmk.qrcode.log.debug") + if (NumberUtils.isDigits(totpStep)) step = totpStep.trim() + if (NumberUtils.isDigits(totpOffset)) offset = totpOffset.trim() + if ("true".equals(debugEnable, true)) debug = "true" + return mapOf("rootkey" to rootkey, "iv" to iv, "prefix" to prefix, "step" to step, "offset" to offset, "debug" to debug) + } + + override fun encodeCode(uid: String): ApiResponse { var resp = ApiResponse() - val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey") - val iv = systemUtilService.getBusinessValue("aes.cfb.iv") - if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) { + val config = qrcodeConfig() + val rootkey = config["rootkey"] + val iv = config["iv"] + val prefix = config["prefix"] ?: "" + val debug = "true" == config["debug"] + + if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) { resp.retcode = 1 resp.retmsg = "秘钥未配置" return resp @@ -86,16 +106,25 @@ class QRCodeServiceImpl:QRCodeService{ return resp } - 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 = qrcode - return resp + try { + val handle = QrCode.builder() + .rootKey(rootkey) + .iv(iv) + .uid(uid) + .card(cityCard.cardno, cityCard.busCardType ?: "80") + .prefix(prefix) + .debug(debug) + .create() + val qrcode = handle.qrcode() + resp.retcode = 0 + resp.retmsg = qrcode + return resp + } catch (ex: Exception) { + ex.printStackTrace() + resp.retcode = 1 + resp.retmsg = "二维码生成失败!${ex.message}" + return resp + } // val totp = QrCodeTotpUtil.generateTOTP(muser.secertkey) // val rowdata = QrcodeRawData() @@ -117,122 +146,278 @@ class QRCodeServiceImpl:QRCodeService{ override fun encodeCode(param: QrcodeParam): ApiResponse { val resp = ApiResponse() val uid = param.uid - val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey") - val iv = systemUtilService.getBusinessValue("aes.cfb.iv") + val config = qrcodeConfig() + val rootkey = config["rootkey"] + val iv = config["iv"] + val prefix = config["prefix"] ?: "" + val debug = "true" == config["debug"] + if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) { resp.retcode = 1 resp.retmsg = "秘钥未配置" return resp } - val totp = QrCodeTotpUtil.generateTOTP(param.secertkey) - val rowdata = QrcodeRawData() - rowdata.userid = param.userid - rowdata.setTotp(totp) - val orgData = Gson().toJson(rowdata) - val publicKey = RSAKeysGenerate.getPublicKey(param.rsapublic) - val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData)) - val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding") + val userSec = mobileApiService.findUserSecretByUid(uid) + if (null == userSec) { + resp.retcode = 1 + resp.retmsg = "识别手机用户身份失败!" + return resp + } else if (userSec.userid != param.userid) { + resp.retcode = 1 + resp.retmsg = "手机用户身份识别异常!" + return resp + } + val bankCard = cardService.getBankcardByUserid(param.userid) + if (null == bankCard) { + resp.retcode = 1 + resp.retmsg = "用户未绑定银行卡" + return resp + } else if (!bankCard.signed) { + resp.retcode = 1 + resp.retmsg = "银行卡未签约" + return resp + } + 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 + } + + try { + val handle = QrCode.builder() + .rootKey(rootkey) + .iv(iv) + .uid(uid) + .card(cityCard.cardno, cityCard.busCardType ?: "80") + .prefix(prefix) + .debug(debug) + .create() + val qrcode = handle.qrcode() + resp.retcode = 0 + resp.retmsg = qrcode + return resp + } catch (ex: Exception) { + ex.printStackTrace() + resp.retcode = 1 + resp.retmsg = "二维码生成失败!${ex.message}" + return resp + } - val key = MD5.encodeByMD5ToURLSafeBase64(qrcode) - redisTemplate.opsForValue().set(key,qrcode, Duration.ofSeconds(20)) - resp.retcode = 0 - resp.retmsg = key - return resp +// val totp = QrCodeTotpUtil.generateTOTP(param.secertkey) +// val rowdata = QrcodeRawData() +// rowdata.userid = param.userid +// rowdata.setTotp(totp) +// val orgData = Gson().toJson(rowdata) +// val publicKey = RSAKeysGenerate.getPublicKey(param.rsapublic) +// val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData)) +// val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding") +// +// val key = MD5.encodeByMD5ToURLSafeBase64(qrcode) +// redisTemplate.opsForValue().set(key,qrcode, Duration.ofSeconds(20)) +// resp.retcode = 0 +// resp.retmsg = key +// return resp } - override fun decodeCode(qrcode :String): DoorQrcodeResponse { + override fun decodeCode(qrcode: String): DoorQrcodeResponse { + val resp = DoorQrcodeResponse() + val config = qrcodeConfig() + val rootkey = config["rootkey"] + val iv = config["iv"] + val prefix = config["prefix"] ?: "" + val debug = "true" == config["debug"] + val totpoffset = config["offset"] + var offset = 3 + if (NumberUtils.isDigits(totpoffset)) { + offset = totpoffset!!.toInt() + } + if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) { + resp.retcode = 1 + resp.retmsg = "秘钥未配置" + return resp + } + println("vtoken=[" + qrcode + "],length=[" + qrcode.length + "]") //解密 - var resp = DoorQrcodeResponse() + var data = HashMap(0) try { - val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey") - val iv = systemUtilService.getBusinessValue("aes.cfb.iv") - val totpoffset = systemUtilService.getBusinessValue("aes.cfb.totp.offset") - var offset = 3 - if (NumberUtils.isDigits(totpoffset)) { - offset = Integer.valueOf(totpoffset).toInt() - } - if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) { - resp.retcode = 1 - resp.retmsg = "秘钥未配置" - return resp - } + val handle = QrCode.builder().rootKey(rootkey).iv(iv).prefix(prefix).debug(debug).create() + //fixme:解码已校验totp + data = handle.decodeWithTotpCheck(qrcode, offset) as HashMap + } catch (ex: Exception) { + ex.printStackTrace() + resp.retcode = 1 + resp.retmsg = "二维码解析报错,${ex.message}" + return resp + } + val uid = data[QrCode.FIELD_UID] + val cardno = data[QrCode.FIELD_CARDNO] + val cardtype = data[QrCode.FIELD_CARDTYPE] //公交卡类型 + if (uid.isNullOrEmpty() || cardno.isNullOrEmpty() || cardtype.isNullOrEmpty()) { + resp.retcode = 1 + resp.retmsg = "二维码解析后数据异常!" + return resp + } + val userSec = mobileApiService.findUserSecretByUid(uid) + val cityCard = cardService.findCardByCardnoAndCardtype(cardno, ConstantUtil.CARDTYPE_CITIZENCARD) + if(null==userSec || null == cityCard || cityCard.userid != userSec.userid){ + resp.retcode = 1 + resp.retmsg = "二维码用户信息错误,请刷新二维码后重试" + return resp + } + val person = userService.findPersonByUserid(cityCard.userid) + val bankcard = mobileApiService.findCardByUseridAndCardphyid(cityCard.userid, ConstantUtil.CARDTYPE_BANKCARD, cityCard.cardphyid) + if (null == person || null == bankcard) { + resp.retcode = 8 + resp.retmsg = "用户市民卡信息异常" + return resp + } - println("vtoken=[" + qrcode + "],length=[" + qrcode.length + "]") - val encdataBack = AesUtil.decryptCFB(qrcode, rootkey, iv, "AES/CFB/NoPadding") - if (encdataBack == null) { - resp.retcode = 1 - resp.retmsg = "二维码解析失败" - return resp - } - val uid = encdataBack.substring(0, encdataBack.indexOf(":")) - val muser = mobileApiService.findUserById(uid) - if(muser==null||TradeDict.STATUS_NORMAL!=muser.status){ - resp.retcode = 1 - resp.retmsg = "用户不存在或状态异常" - return resp - } - val seed32 = muser.secertkey - val rawEncdata = encdataBack.substring(encdataBack.indexOf(":") + 1) - - val privateKey = RSAKeysGenerate.getPrivateKey(muser.rsaprivate) - val rawdataBack = RSAKeysGenerate.decryptbyte(privateKey, rawEncdata) - logger.info("rawdata_back=$rawdataBack") - - val rawData = Gson().fromJson(rawdataBack, QrcodeRawData::class.java) - logger.info("userid=" + rawData.userid) - if (null == rawData || rawData.userid.isNullOrEmpty()) { - resp.retcode = 1 - resp.retmsg = "用户不存在" - return resp - } - val person = userService.findOnePersonByUserid(rawData.userid) + resp.retcode = 0 + resp.retmsg = "OK" + resp.userid = person.userid + resp.username = person.name + resp.sex = person.sex + resp.idtype = person.idtype + resp.idno = person.idno + resp.phone = person.mobile - val bankcard = mobileApiService.findCardByUserid(person.userid) - if (null == bankcard) { - resp.retcode = 9 - resp.retmsg = "二维码识别错误,手机绑定的银行卡已注销" - return resp - } - //银行卡必定存在对应的市民卡 - val citycard = mobileApiService.findCardByUseridAndCardphyid(person.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankcard.cardphyid) - if (null == citycard) { - resp.retcode = 8 - resp.retmsg = "二维码识别异常" - return resp - } + resp.citycardno = cityCard.cardno + resp.cardphyid = cityCard.cardphyid + resp.expiredate = cityCard.expiredate + resp.cardstatus = cityCard.status + resp.transstatus = cityCard.transStatus + resp.bankcardno = bankcard.cardno + return resp + } - val verifyBarcode = QrCodeTotpUtil.verifyCode(rawData.totp, seed32, offset) - return if (verifyBarcode) { - resp.retcode = 0 - resp.retmsg = "OK" - resp.userid = person.userid - resp.username = person.name - resp.sex = person.sex - resp.idtype = person.idtype - resp.idno = person.idno - resp.phone = person.mobile - - resp.citycardno = citycard.cardno - resp.cardphyid = citycard.cardphyid - resp.expiredate = citycard.expiredate - resp.cardstatus = citycard.status - resp.transstatus = citycard.transStatus - resp.bankcardno = bankcard.cardno - resp + override fun decodeCode(qrcode: String, chkTotp: Boolean, cardno: String?, tac: String?, amount: Int?, transdate: String?, transtime: String?): DoorQrcodeResponse { + val resp = DoorQrcodeResponse() + val config = qrcodeConfig() + val rootkey = config["rootkey"] + val iv = config["iv"] + val prefix = config["prefix"] ?: "" + val debug = "true" == config["debug"] + val totpoffset = config["offset"] + var offset = 3 + if (NumberUtils.isDigits(totpoffset)) { + offset = totpoffset!!.toInt() + } + if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) { + resp.retcode = 1 + resp.retmsg = "秘钥未配置" + return resp + } + println("citizencard QRcode=[" + qrcode + "],length=[" + qrcode.length + "]") + + //解密 + var data = HashMap(0) + try { + val handle = QrCode.builder().rootKey(rootkey).iv(iv).prefix(prefix).debug(debug).create() + if (chkTotp) { + //联机解码需校验totp + data = handle.decodeWithTotpCheck(qrcode, offset) as HashMap } else { - resp.retcode = 1 - resp.retmsg = "二维码校验失败" - resp + //脱机码上传无需校验totp + data = handle.decodeWithoutTotpCheck(qrcode) as HashMap + if (!data.isNullOrEmpty()) { + if (!StringUtil.isEmpty(cardno) && cardno!!.trim() != data[QrCode.FIELD_CARDNO]) { + resp.retcode = 1 + resp.retmsg = "市民卡号错误!" + return resp //验证卡号是否一致 + } + if (handle.tac(amount, transdate, transtime) != tac) { + resp.retcode = 1 + resp.retmsg = "流水tac验证错误!" + return resp //验证脱机流水tac是否一致 + } + } } - } catch (e: Exception) { - e.printStackTrace() + } catch (ex: Exception) { + ex.printStackTrace() + resp.retcode = 1 + resp.retmsg = "二维码解析报错,${ex.message}" + return resp + } + + val uid = data[QrCode.FIELD_UID] + val cardno = data[QrCode.FIELD_CARDNO] + val cardtype = data[QrCode.FIELD_CARDTYPE] //公交卡类型 + if (uid.isNullOrEmpty() || cardno.isNullOrEmpty() || cardtype.isNullOrEmpty()) { + resp.retcode = 1 + resp.retmsg = "二维码解析后数据异常!" + return resp + } + val userSec = mobileApiService.findUserSecretByUid(uid) + val cityCard = cardService.findCardByCardnoAndCardtype(cardno, ConstantUtil.CARDTYPE_CITIZENCARD) + if (null == userSec || null == cityCard || cityCard.userid != userSec.userid) { resp.retcode = 1 - resp.retmsg = "二维码识别异常" + resp.retmsg = "二维码用户信息错误,请刷新二维码后重试" return resp } + val person = userService.findPersonByUserid(cityCard.userid) + val bankcard = mobileApiService.findCardByUseridAndCardphyid(cityCard.userid, ConstantUtil.CARDTYPE_BANKCARD, cityCard.cardphyid) + if (null == person || null == bankcard) { + resp.retcode = 8 + resp.retmsg = "用户市民卡信息异常" + return resp + } + + resp.retcode = 0 + resp.retmsg = "OK" + resp.userid = person.userid + resp.username = person.name + resp.sex = person.sex + resp.idtype = person.idtype + resp.idno = person.idno + resp.phone = person.mobile + + resp.citycardno = cityCard.cardno + resp.cardphyid = cityCard.cardphyid + resp.expiredate = cityCard.expiredate + resp.cardstatus = cityCard.status + resp.transstatus = cityCard.transStatus + resp.bankcardno = bankcard.cardno + return resp + } + + override fun queryQRCodeKeys(timestamp: String): CitizenQrcodeKey { + val resp = CitizenQrcodeKey() + val offsetSec = DateUtil.getInterval(timestamp, DateUtil.getNow(DateUtil.DATETIME_FMT)) / 1000 //系统时间-请求时间 秒 + val config = qrcodeConfig() + val rootkey = config["rootkey"] + val iv = config["iv"] + if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) { + resp.retcode = 1 + resp.retmsg = "秘钥未配置" + return resp + } + resp.retcode = 0 + resp.retmsg = "success" + resp.rootKey = rootkey + resp.iv = iv + resp.prefix = config["prefix"] ?: "" + resp.offsetSec = offsetSec + resp.totpStep = config["step"]?.toInt() ?: 5 + resp.totpOffset = config["offset"]?.toInt() ?: 3 + return resp } } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt index d8b21ce8..a9b2db26 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt @@ -1,6 +1,7 @@ package com.supwisdom.dlpay.api.service import com.supwisdom.dlpay.api.bean.ApiResponse +import com.supwisdom.dlpay.api.bean.CitizenQrcodeKey import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse import com.supwisdom.dlpay.api.bean.QrcodeParam @@ -8,4 +9,6 @@ interface QRCodeService { fun encodeCode(uid: String): ApiResponse fun encodeCode(param: QrcodeParam): ApiResponse fun decodeCode(qrcode: String): DoorQrcodeResponse + fun decodeCode(qrcode: String, chkTotp: Boolean, cardno: String?, tac: String?, amount:Int?, transdate:String?, transtime:String?): DoorQrcodeResponse + fun queryQRCodeKeys(timestamp: String): CitizenQrcodeKey } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt index 8de6f1e1..1aab5766 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt @@ -3,6 +3,7 @@ package com.supwisdom.dlpay.mobile import com.supwisdom.dlpay.agent.citizencard.YnrccUtil import com.supwisdom.dlpay.agent.service.CitizencardPayService import com.supwisdom.dlpay.api.bean.JsonResult +import com.supwisdom.dlpay.api.bean.SignBxyParam import com.supwisdom.dlpay.api.service.QRCodeService import com.supwisdom.dlpay.api.service.UserService import com.supwisdom.dlpay.api.util.MobileNumberCheck @@ -611,10 +612,15 @@ class ApiV1 { if (resp.code != "0000") { return JsonResult.error(resp.message) } - card.signed = true user.signedtime = DateUtil.getNow() - mobileApiService.saveCard(card) mobileApiService.saveUser(user) + mobileApiService.signBxy(card, SignBxyParam().apply { + this.uid=user.uid + this.userid = user.userid + this.secertkey = user.secertkey + this.rsapublic = user.rsapublic + this.rsaprivate = user.rsaprivate + }) signed = TradeDict.STATUS_YES } else { return JsonResult.error("请先绑定银行卡") diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt index bbe2e840..600f4698 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt @@ -3,6 +3,7 @@ package com.supwisdom.dlpay.mobile.service import com.supwisdom.dlpay.api.bean.BaseResp import com.supwisdom.dlpay.api.bean.SignBxyParam import com.supwisdom.dlpay.api.domain.TCard +import com.supwisdom.dlpay.api.domain.TUserSecret import com.supwisdom.dlpay.mobile.domain.TBMobileUser import com.supwisdom.dlpay.mobile.domain.TBPages @@ -30,4 +31,6 @@ interface MobileApiService { fun findByUseridAndStatus(userid:String,status:String):List? fun findCardByUseridAndCardphyid(userid: String, cardtype: String, cardphyid: String?): TCard? + + fun findUserSecretByUid(uid:String):TUserSecret? } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt index 2aebcee0..e5e6ec60 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt @@ -209,4 +209,8 @@ class MobileApiServiceImpl : MobileApiService { cardDao.findBankcardByCitizencard(userid, cardtype, cardphyid) } } + + override fun findUserSecretByUid(uid: String): TUserSecret? { + return userSecretDao.getByUid(uid) + } } \ No newline at end of file