wangqifan vor 3 Jahren
Ursprung
Commit
a3a7be476f
42 geänderte Dateien mit 2402 neuen und 54 gelöschten Zeilen
  1. 175 0
      src/main/java/cn/com/sandpay/cashier/sdk/CertUtil.java
  2. 411 0
      src/main/java/cn/com/sandpay/cashier/sdk/CryptoUtil.java
  3. 325 0
      src/main/java/cn/com/sandpay/cashier/sdk/HttpClient.java
  4. 39 0
      src/main/java/cn/com/sandpay/cashier/sdk/RandomStringGenerator.java
  5. 278 0
      src/main/java/cn/com/sandpay/cashier/sdk/SDKConfig.java
  6. 132 0
      src/main/java/cn/com/sandpay/cashier/sdk/SDKUtil.java
  7. 25 0
      src/main/java/com/izouma/nineth/config/SandPayConfig.java
  8. 16 0
      src/main/java/com/izouma/nineth/config/SandPayProperties.java
  9. 1 0
      src/main/java/com/izouma/nineth/domain/GiftOrder.java
  10. 1 0
      src/main/java/com/izouma/nineth/domain/MintOrder.java
  11. 1 4
      src/main/java/com/izouma/nineth/domain/Order.java
  12. 14 0
      src/main/java/com/izouma/nineth/dto/airDrop/AirDropDTO.java
  13. 2 1
      src/main/java/com/izouma/nineth/enums/PayMethod.java
  14. 17 0
      src/main/java/com/izouma/nineth/enums/SandPayMethod.java
  15. 1 0
      src/main/java/com/izouma/nineth/event/OrderNotifyEvent.java
  16. 1 1
      src/main/java/com/izouma/nineth/listener/MintListener.java
  17. 1 0
      src/main/java/com/izouma/nineth/repo/ErrorOrder.java
  18. 2 0
      src/main/java/com/izouma/nineth/repo/OrderRepo.java
  19. 2 0
      src/main/java/com/izouma/nineth/repo/UserRepo.java
  20. 1 0
      src/main/java/com/izouma/nineth/service/GiftOrderService.java
  21. 1 4
      src/main/java/com/izouma/nineth/service/MintOrderService.java
  22. 652 0
      src/main/java/com/izouma/nineth/service/SandPayService.java
  23. 12 8
      src/main/java/com/izouma/nineth/service/UserService.java
  24. 1 0
      src/main/java/com/izouma/nineth/utils/excel/PayMethodConverter.java
  25. 29 0
      src/main/java/com/izouma/nineth/utils/excel/UploadDataListener.java
  26. 1 0
      src/main/java/com/izouma/nineth/web/OrderController.java
  27. 1 0
      src/main/java/com/izouma/nineth/web/OrderNotifyController.java
  28. 38 14
      src/main/java/com/izouma/nineth/web/OrderPayController.java
  29. 87 0
      src/main/java/com/izouma/nineth/web/SandPayController.java
  30. 2 2
      src/main/java/com/izouma/nineth/web/StatisticController.java
  31. 22 0
      src/main/resources/application.yaml
  32. BIN
      src/main/resources/cert/6888806044146.cer
  33. BIN
      src/main/resources/cert/6888806044146.pfx
  34. BIN
      src/main/resources/cert/sand.cer
  35. 61 2
      src/test/java/com/izouma/nineth/service/AirDropServiceTest.java
  36. 1 0
      src/test/java/com/izouma/nineth/service/AssetMintServiceTest.java
  37. 24 10
      src/test/java/com/izouma/nineth/service/AssetServiceTest.java
  38. 3 2
      src/test/java/com/izouma/nineth/service/IdentityAuthServiceTest.java
  39. 1 0
      src/test/java/com/izouma/nineth/service/MintOrderServiceTest.java
  40. 4 0
      src/test/java/com/izouma/nineth/service/OrderServiceTest.java
  41. 4 4
      src/test/java/com/izouma/nineth/service/StatisticServiceTest.java
  42. 13 2
      src/test/java/com/izouma/nineth/service/UserServiceTest.java

+ 175 - 0
src/main/java/cn/com/sandpay/cashier/sdk/CertUtil.java

@@ -0,0 +1,175 @@
+/**
+ *
+ * Licensed Property to Sand Co., Ltd.
+ * 
+ * (C) Copyright of Sand Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author           Date           Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   企业产品团队       2016-10-12       证书工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+//支付宝生活号/支付宝小程序
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ * @ClassName: CertUtil
+ * @Description: sdk证书工具类,主要用于对证书的加载和使用
+ * @version 2.0.0
+ */
+public class CertUtil {
+	
+	public static final String PUBLIC_KEY = "public_key";
+	public static final String PRIVATE_KEY = "private_key";
+
+	private static final Logger logger = LoggerFactory.getLogger(CertUtil.class);
+
+	private static final ConcurrentHashMap<String, Object> keys = new ConcurrentHashMap<String, Object>();
+
+	public static void init(String publicKeyPath, String privateKeyPath, String keyPassword) throws Exception {
+		// 加载私钥
+		initPulbicKey(publicKeyPath);
+		// 加载公钥
+		initPrivateKey(privateKeyPath, keyPassword);
+	}
+
+	public static PublicKey getPublicKey() {
+		return (PublicKey) keys.get(PUBLIC_KEY);
+	}
+
+	public static PrivateKey getPrivateKey() {
+		return (PrivateKey) keys.get(PRIVATE_KEY);
+	}
+
+	private static void initPulbicKey(String publicKeyPath) throws Exception {
+
+		String classpathKey = "classpath:";
+		if (publicKeyPath != null) {
+			try {
+				InputStream inputStream = null;
+				if (publicKeyPath.startsWith(classpathKey)) {
+					inputStream = CertUtil.class.getClassLoader()
+							.getResourceAsStream(publicKeyPath.substring(classpathKey.length()));
+				} else {
+					inputStream = new FileInputStream(publicKeyPath);
+				}
+				PublicKey publicKey = CertUtil.getPublicKey(inputStream);
+				keys.put(PUBLIC_KEY, publicKey);
+			} catch (Exception e) {
+				logger.error("无法加载公钥[{}]", new Object[] { publicKeyPath });
+				logger.error(e.getMessage(), e);
+				throw e;
+			}
+		}
+	}
+
+	private static void initPrivateKey(String privateKeyPath, String keyPassword) throws Exception {
+
+		String classpathKey = "classpath:";
+
+		try {
+			InputStream inputStream = null;
+			if (privateKeyPath.startsWith(classpathKey)) {
+				inputStream = CertUtil.class.getClassLoader()
+						.getResourceAsStream(privateKeyPath.substring(classpathKey.length()));
+			} else {
+				inputStream = new FileInputStream(privateKeyPath);
+			}
+			PrivateKey privateKey = CertUtil.getPrivateKey(inputStream, keyPassword);
+			keys.put(PRIVATE_KEY, privateKey);
+		} catch (Exception e) {
+			logger.error("无法加载本地私银[" + privateKeyPath + "]");
+			logger.error(e.getMessage(), e);
+			throw e;
+		}
+	}
+
+	
+	public static PublicKey getPublicKey(InputStream inputStream) throws Exception {
+		try {
+
+			CertificateFactory cf = CertificateFactory.getInstance("X.509");
+			X509Certificate oCert = (X509Certificate) cf.generateCertificate(inputStream);
+			PublicKey publicKey = oCert.getPublicKey();
+			return publicKey;
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new Exception("读取公钥异常");
+		} finally {
+			try {
+				if (inputStream != null) {
+					inputStream.close();
+				}
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	
+	/**
+	 * 获取私钥对象
+	 * 
+	 * @param inputStream
+	 *            私钥输入流
+	 * @param keyAlgorithm
+	 *            密钥算法
+	 * @return 私钥对象
+	 * @throws Exception
+	 */
+	public static PrivateKey getPrivateKey(InputStream inputStream, String password) throws Exception {
+		try {
+			KeyStore ks = KeyStore.getInstance("PKCS12");
+			char[] nPassword = null;
+			if ((password == null) || password.trim().equals("")) {
+				nPassword = null;
+			} else {
+				nPassword = password.toCharArray();
+			}
+
+			ks.load(inputStream, nPassword);
+			Enumeration<String> enumas = ks.aliases();
+			String keyAlias = null;
+			if (enumas.hasMoreElements()) {
+				keyAlias = (String) enumas.nextElement();
+			}
+
+			PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, nPassword);
+			return privateKey;
+		} catch (FileNotFoundException e) {
+			throw new Exception("私钥路径文件不存在");
+		} catch (IOException e) {
+			throw new Exception(e);
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception("生成私钥对象异常");
+		} finally {
+			try {
+				if (inputStream != null) {
+					inputStream.close();
+				}
+			} catch (IOException e) {
+			}
+		}
+	}
+
+}

+ 411 - 0
src/main/java/cn/com/sandpay/cashier/sdk/CryptoUtil.java

@@ -0,0 +1,411 @@
+/**
+ *
+ * Licensed Property to Sand Co., Ltd.
+ * 
+ * (C) Copyright of Sand Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author           Date           Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   企业产品团队       2016-10-12       加解密工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.*;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.*;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+
+/**
+ * @ClassName: CryptoUtil
+ * @Description: sdk加解密工具类,主要用于签名、验证、RSA加解密等
+ * @version 2.0.0
+ */
+
+public class CryptoUtil {
+
+	public static  Logger logger = LoggerFactory.getLogger(CryptoUtil.class);
+	
+	/**
+	 * 数字签名函数入口
+	 * 
+	 * @param plainBytes
+	 *            待签名明文字节数组
+	 * @param privateKey
+	 *            签名使用私钥
+	 * @param signAlgorithm
+	 *            签名算法
+	 * @return 签名后的字节数组
+	 * @throws Exception
+	 */
+	public static byte[] digitalSign(byte[] plainBytes, PrivateKey privateKey, String signAlgorithm) throws Exception {
+		try {
+			Signature signature = Signature.getInstance(signAlgorithm);
+			signature.initSign(privateKey);
+			signature.update(plainBytes);
+			byte[] signBytes = signature.sign();
+
+			return signBytes;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("数字签名时没有[%s]此类算法", signAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("数字签名时私钥无效", e);
+		} catch (SignatureException e) {
+			throw new Exception("数字签名时出现异常", e);
+		}
+	}
+
+	/**
+	 * 验证数字签名函数入口
+	 * 
+	 * @param plainBytes
+	 *            待验签明文字节数组
+	 * @param signBytes
+	 *            待验签签名后字节数组
+	 * @param publicKey
+	 *            验签使用公钥
+	 * @param signAlgorithm
+	 *            签名算法
+	 * @return 验签是否通过
+	 * @throws Exception
+	 */
+	public static boolean verifyDigitalSign(byte[] plainBytes, byte[] signBytes, PublicKey publicKey, String signAlgorithm) throws Exception {
+		boolean isValid = false;
+		try {
+			Signature signature = Signature.getInstance(signAlgorithm);
+			signature.initVerify(publicKey);
+			signature.update(plainBytes);
+			isValid = signature.verify(signBytes);
+			return isValid;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("验证数字签名时没有[%s]此类算法", signAlgorithm), e);
+		} catch (InvalidKeyException e) {
+			throw new Exception("验证数字签名时公钥无效", e);
+		} catch (SignatureException e) {
+			throw new Exception("验证数字签名时出现异常", e);
+		}
+	}
+
+	/**
+	 * 验证数字签名函数入口
+	 * 
+	 * @param plainBytes
+	 *            待验签明文字节数组
+	 * @param signBytes
+	 *            待验签签名后字节数组
+	 * @param publicKey
+	 *            验签使用公钥
+	 * @param signAlgorithm
+	 *            签名算法
+	 * @return 验签是否通过
+	 * @throws Exception
+	 */
+	public static boolean verifyDigitalSign(byte[] plainBytes, byte[] signBytes, X509Certificate cert, String signAlgorithm) throws Exception {
+		boolean isValid = false;
+		try {
+			Signature signature = Signature.getInstance(signAlgorithm);
+			signature.initVerify(cert);
+			signature.update(plainBytes);
+			isValid = signature.verify(signBytes);
+			return isValid;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("验证数字签名时没有[%s]此类算法", signAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("验证数字签名时公钥无效", e);
+		} catch (SignatureException e) {
+			throw new Exception("验证数字签名时出现异常", e);
+		}
+	}
+
+	/**
+	 * RSA加密
+	 * 
+	 * @param plainBytes
+	 *            明文字节数组
+	 * @param publicKey
+	 *            公钥
+	 * @param keyLength
+	 *            密钥bit长度
+	 * @param reserveSize
+	 *            padding填充字节数,预留11字节
+	 * @param cipherAlgorithm
+	 *            加解密算法,一般为RSA/ECB/PKCS1Padding
+	 * @return 加密后字节数组,不经base64编码
+	 * @throws Exception
+	 */
+	public static byte[] RSAEncrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm)
+			throws Exception {
+		int keyByteSize = keyLength / 8; // 密钥字节数
+		int encryptBlockSize = keyByteSize - reserveSize; // 加密块大小=密钥字节数-padding填充字节数
+		int nBlock = plainBytes.length / encryptBlockSize;// 计算分段加密的block数,向上取整
+		if ((plainBytes.length % encryptBlockSize) != 0) { // 余数非0,block数再加1
+			nBlock += 1;
+		}
+
+		try {
+			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+
+			// 输出buffer,大小为nBlock个keyByteSize
+			ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
+			// 分段加密
+			for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
+				int inputLen = plainBytes.length - offset;
+				if (inputLen > encryptBlockSize) {
+					inputLen = encryptBlockSize;
+				}
+
+				// 得到分段加密结果
+				byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
+				// 追加结果到输出buffer中
+				outbuf.write(encryptedBlock);
+			}
+
+			outbuf.flush();
+			outbuf.close();
+			return outbuf.toByteArray();
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
+		} catch (NoSuchPaddingException e) {
+			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("无效密钥", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new Exception("加密块大小不合法", e);
+		} catch (BadPaddingException e) {
+			throw new Exception("错误填充模式", e);
+		} catch (IOException e) {
+			throw new Exception("字节输出流异常", e);
+		}
+	}
+
+	/**
+	 * RSA解密
+	 * 
+	 * @param encryptedBytes
+	 *            加密后字节数组
+	 * @param privateKey
+	 *            私钥
+	 * @param keyLength
+	 *            密钥bit长度
+	 * @param reserveSize
+	 *            padding填充字节数,预留11字节
+	 * @param cipherAlgorithm
+	 *            加解密算法,一般为RSA/ECB/PKCS1Padding
+	 * @return 解密后字节数组,不经base64编码
+	 * @throws Exception
+	 */
+	public static byte[] RSADecrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm)
+			throws Exception {
+		int keyByteSize = keyLength / 8; // 密钥字节数
+		int decryptBlockSize = keyByteSize - reserveSize; // 解密块大小=密钥字节数-padding填充字节数
+		int nBlock = encryptedBytes.length / keyByteSize;// 计算分段解密的block数,理论上能整除
+
+		try {
+			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+			cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+			// 输出buffer,大小为nBlock个decryptBlockSize
+			ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
+			// 分段解密
+			for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
+				// block大小: decryptBlock 或 剩余字节数
+				int inputLen = encryptedBytes.length - offset;
+				if (inputLen > keyByteSize) {
+					inputLen = keyByteSize;
+				}
+
+				// 得到分段解密结果
+				byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
+				// 追加结果到输出buffer中
+				outbuf.write(decryptedBlock);
+			}
+
+			outbuf.flush();
+			outbuf.close();
+			return outbuf.toByteArray();
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("没有[%s]此类解密算法", cipherAlgorithm));
+		} catch (NoSuchPaddingException e) {
+			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("无效密钥", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new Exception("解密块大小不合法", e);
+		} catch (BadPaddingException e) {
+			throw new Exception("错误填充模式", e);
+		} catch (IOException e) {
+			throw new Exception("字节输出流异常", e);
+		}
+	}
+	
+	public static PublicKey toPublicKey(BigInteger exponent,BigInteger modulus) throws Exception {
+		
+		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+		RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(modulus, exponent);
+		PublicKey key = keyFactory.generatePublic(pubSpec);
+		return key;
+	}
+	
+	public static PrivateKey toPrivateKey(BigInteger exponent,BigInteger modulus) throws Exception {
+		
+		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+		RSAPrivateKeySpec prispec = new RSAPrivateKeySpec(modulus, exponent);
+		PrivateKey key = keyFactory.generatePrivate(prispec);
+		return key;
+	}
+
+	
+	/**
+	 * AES加密
+	 * 
+	 * @param plainBytes
+	 *            明文字节数组
+	 * @param keyBytes
+	 *            密钥字节数组
+	 * @param keyAlgorithm
+	 *            密钥算法
+	 * @param cipherAlgorithm
+	 *            加解密算法
+	 * @param IV
+	 *            随机向量
+	 * @return 加密后字节数组,不经base64编码
+	 * @throws Exception
+	 */
+	public static byte[] AESEncrypt(byte[] plainBytes, byte[] keyBytes, String keyAlgorithm, String cipherAlgorithm, String IV)
+			throws Exception {
+		try {
+			// AES密钥长度为128bit、192bit、256bit,默认为128bit
+			if (keyBytes.length % 8 != 0 || keyBytes.length < 16 || keyBytes.length > 32) {
+				throw new Exception("AES密钥长度不合法");
+			}
+
+			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+			SecretKey secretKey = new SecretKeySpec(keyBytes, keyAlgorithm);
+			if (StringUtils.trimToNull(IV) != null) {
+				IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes());
+				cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
+			} else {
+				cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+			}
+
+			byte[] encryptedBytes = cipher.doFinal(plainBytes);
+
+			return encryptedBytes;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
+		} catch (NoSuchPaddingException e) {
+			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("无效密钥", e);
+		} catch (InvalidAlgorithmParameterException e) {
+			throw new Exception("无效密钥参数", e);
+		} catch (BadPaddingException e) {
+			throw new Exception("错误填充模式", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new Exception("加密块大小不合法", e);
+		}
+	}
+
+	/**
+	 * AES解密
+	 * 
+	 * @param encryptedBytes
+	 *            密文字节数组,不经base64编码
+	 * @param keyBytes
+	 *            密钥字节数组
+	 * @param keyAlgorithm
+	 *            密钥算法
+	 * @param cipherAlgorithm
+	 *            加解密算法
+	 * @param IV
+	 *            随机向量
+	 * @return 解密后字节数组
+	 * @throws Exception
+	 */
+	public static byte[] AESDecrypt(byte[] encryptedBytes, byte[] keyBytes, String keyAlgorithm, String cipherAlgorithm, String IV)
+			throws Exception {
+		try {
+			// AES密钥长度为128bit、192bit、256bit,默认为128bit
+			if (keyBytes.length % 8 != 0 || keyBytes.length < 16 || keyBytes.length > 32) {
+				throw new Exception("AES密钥长度不合法");
+			}
+
+			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+			SecretKey secretKey = new SecretKeySpec(keyBytes, keyAlgorithm);
+			if (IV != null && StringUtils.trimToNull(IV) != null) {
+				IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes());
+				cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
+			} else {
+				cipher.init(Cipher.DECRYPT_MODE, secretKey);
+			}
+
+			byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
+
+			return decryptedBytes;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
+		} catch (NoSuchPaddingException e) {
+			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("无效密钥", e);
+		} catch (InvalidAlgorithmParameterException e) {
+			throw new Exception("无效密钥参数", e);
+		} catch (BadPaddingException e) {
+			throw new Exception("错误填充模式", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new Exception("解密块大小不合法", e);
+		}
+	}
+	
+	public static  byte[] hexString2ByteArr(String hexStr) {
+		return new BigInteger(hexStr, 16).toByteArray();
+	}
+	public static final byte[] hexStrToBytes(String s) {
+		byte[] bytes; 
+		bytes = new byte[s.length() / 2];
+		for (int i = 0; i < bytes.length; i++) { 
+			bytes[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * 		i + 2), 16);
+		} 
+		return bytes;
+	}
+	/**
+	 * 字符数组16进制字符
+	 * 
+	 * @param bytes
+	 * @return
+	 */
+	public static String bytes2string(byte[] bytes, int radix) {
+		int size = 2;
+		if (radix == 2) {
+			size = 8;
+		}
+		StringBuilder sb = new StringBuilder(bytes.length * size);
+		for (int i = 0; i < bytes.length; i++) {
+			int integer = bytes[i];
+			while (integer < 0) {
+				integer = integer + 256;
+			}
+			String str = Integer.toString(integer, radix);
+			sb.append(StringUtils.leftPad(str.toUpperCase(), size, "0"));
+		}
+		return sb.toString();
+	}
+	
+	
+}

+ 325 - 0
src/main/java/cn/com/sandpay/cashier/sdk/HttpClient.java

@@ -0,0 +1,325 @@
+/**
+ *
+ * Licensed Property to Sand Co., Ltd.
+ * 
+ * (C) Copyright of Sand Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author           Date           Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   企业产品团队       2016-10-12       Http通讯工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.*;
+import java.io.IOException;
+import java.net.URL;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.Map.Entry;
+
+
+/**
+ * @ClassName: HttpClient
+ * @Description: 主要用于Http通讯
+ * @version 2.0.0
+ */
+public class HttpClient {
+
+	private static Logger logger = LoggerFactory.getLogger(HttpClient.class);
+
+	private static final String DEFAULT_CHARSET = "UTF-8";
+
+	private static SSLContext sslcontext;
+
+	private static SSLConnectionSocketFactory sslsf;
+
+	public static String doPost(String url, Map<String, String> params, int connectTimeout, int readTimeout)
+			throws IOException {
+		return doPost(url, params, DEFAULT_CHARSET, connectTimeout, readTimeout);
+	}
+
+	public static String doPost(String url, Map<String, String> params, String charset, int connectTimeout,
+			int readTimeout) throws IOException {
+		Map<String, String> headers = new HashMap<String, String>();
+		headers.put("Content-type", "application/x-www-form-urlencoded;charset=" + charset);
+		return doPost(url, headers, params, charset, connectTimeout, readTimeout);
+	}
+
+	public static String doPost(String url, Map<String, String> headers, Map<String, String> params,
+			final String charset, int connectTimeout, int readTimeout) throws IOException {
+
+		URL targetUrl = new URL(url);
+		HttpHost httpHost = new HttpHost(targetUrl.getHost(), targetUrl.getPort(), targetUrl.getProtocol());
+		logger.info("host:" + targetUrl.getHost() + ",port:" + targetUrl.getPort() + ",protocol:"
+				+ targetUrl.getProtocol() + ",path:" + targetUrl.getPath());
+
+		CloseableHttpClient httpclient = getHttpClient(targetUrl);
+
+		try {
+			HttpPost httpPost = getHttpPost(targetUrl, headers, params, charset, connectTimeout, readTimeout);
+
+			String resp = httpclient.execute(httpHost, httpPost, new ResponseHandler<String>() {
+				public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+
+					int status = response.getStatusLine().getStatusCode();
+
+					logger.info("status:["+status+"]");
+					if (status == 200) {
+						return EntityUtils.toString(response.getEntity(), charset);
+					} else {
+						return "";
+					}
+				}
+			});
+			return resp;
+		} finally {
+			httpclient.close();
+		}
+
+	}
+
+	/**
+	 * 执行HTTP GET请求。
+	 * 
+	 * @param url
+	 *            请求地址
+	 * @param params
+	 *            请求参数
+	 * @return 响应字符串
+	 * @throws IOException
+	 */
+	public static String doGet(String url, Map<String, String> params) throws IOException {
+		return doGet(url, params, DEFAULT_CHARSET);
+	}
+
+	/**
+	 * 执行HTTP GET请求。
+	 * 
+	 * @param url
+	 *            请求地址
+	 * @param params
+	 *            请求参数
+	 * @param charset
+	 *            字符集,如UTF-8, GBK, GB2312
+	 * @return 响应字符串
+	 * @throws IOException
+	 */
+	public static String doGet(String url, Map<String, String> params, String charset) throws IOException {
+
+		Map<String, String> headers = new HashMap<String, String>();
+		headers.put("Content-type", "application/x-www-form-urlencoded;charset=" + charset);
+		return doGet(url, headers, params, charset);
+
+	}
+
+	/**
+	 * 
+	 * @param url
+	 * @param headers
+	 * @param params
+	 * @param charset
+	 * @return
+	 * @throws IOException
+	 * @throws ClientProtocolException
+	 */
+	public static String doGet(String url, Map<String, String> headers, Map<String, String> params,
+			final String charset) throws IOException {
+
+		URL targetUrl = new URL(url);
+		CloseableHttpClient httpclient = getHttpClient(targetUrl);
+
+		try {
+
+			HttpGet httpGet = getHttpGet(url, headers, params, charset);
+
+			String resp = httpclient.execute(httpGet, new ResponseHandler<String>() {
+				public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+					int status = response.getStatusLine().getStatusCode();
+
+					logger.info("status:[{}]", new Object[] { status });
+					if (status == 200) {
+						return EntityUtils.toString(response.getEntity(), charset);
+					} else {
+						return "";
+					}
+				}
+			});
+			return resp;
+
+		} finally {
+			httpclient.close();
+		}
+	}
+
+	/**
+	 * 
+	 * @param targetUrl
+	 * @param headers
+	 * @param params
+	 * @param charset
+	 * @param isProxy
+	 * @return
+	 * @throws IOException
+	 */
+	private static HttpGet getHttpGet(String url, Map<String, String> headers, Map<String, String> params,
+			String charset) throws IOException {
+
+		URL targetUrl = buildGetUrl(url, buildQuery(params, charset));
+		HttpGet httpGet = new HttpGet(targetUrl.toString());
+
+		Iterator<Entry<String, String>> iterator = headers.entrySet().iterator();
+		while (iterator.hasNext()) {
+			Entry<String, String> entry = iterator.next();
+			httpGet.setHeader(entry.getKey(), entry.getValue());
+		}
+
+		return httpGet;
+
+	}
+
+	/**
+	 * @param isProxy
+	 * 
+	 * @param targetUrl @param headers @param params @param charset @param
+	 * connectTimeout @param readTimeout @return @throws IOException @throws
+	 */
+	private static HttpPost getHttpPost(URL targetUrl, Map<String, String> headers, Map<String, String> params,
+			String charset, int connectTimeout, int readTimeout) throws IOException {
+
+		HttpPost httpPost = new HttpPost(targetUrl.getPath());
+
+		Iterator<Entry<String, String>> iterator = headers.entrySet().iterator();
+		while (iterator.hasNext()) {
+			Entry<String, String> entry = iterator.next();
+			httpPost.setHeader(entry.getKey(), entry.getValue());
+		}
+
+		RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeout)
+				.setConnectTimeout(connectTimeout) // Connection timeout is the timeout until a connection with the
+													// server is established.
+				.build();
+		httpPost.setConfig(requestConfig);
+
+		StringEntity entity = new StringEntity(buildQuery(params, charset), charset);
+		httpPost.setEntity(entity);
+
+		return httpPost;
+	}
+
+	/**
+	 * 
+	 * @param targetUrl
+	 * @return
+	 */
+	private static CloseableHttpClient getHttpClient(URL targetUrl) {
+
+		CloseableHttpClient httpClient = null;
+		if ("https".equals(targetUrl.getProtocol())) {
+
+			httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
+		} else {
+			httpClient = HttpClients.createDefault();
+		}
+		return httpClient;
+	}
+
+	private static class DefaultTrustManager implements X509TrustManager {
+		public X509Certificate[] getAcceptedIssuers() {
+			return null;
+		}
+
+		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+		}
+
+		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+		}
+	}
+
+	static {
+		try {
+			sslcontext = SSLContext.getInstance("TLS");
+			sslcontext.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom());
+
+			// Allow TLSv1 protocol only
+			sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, new HostnameVerifier() {
+				public boolean verify(String hostname, SSLSession session) {
+					return true;// 默认认证不通过,进行证书校验。
+				}
+			});
+
+			// javax.net.ssl.SSLPeerUnverifiedException: Host name '192.168.92.124' does not
+			// match the certificate subject provided by the peer
+			// (EMAILADDRESS=lsq1015@qq.com, CN=ipay, OU=CMBC, O=XMCMBC, L=Xiamen,
+			// ST=Fujian, C=CN)
+			// at
+			// org.apache.http.conn.ssl.SSLConnectionSocketFactory.verifyHostname(SSLConnectionSocketFactory.java:394)
+
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+	}
+	
+	public static String buildQuery(Map<String, String> params, String charset) throws IOException {
+		 
+		List<NameValuePair> nvps = new LinkedList<NameValuePair>();
+		
+		Set<Entry<String, String>> entries = params.entrySet();
+		for (Entry<String, String> entry : entries) {
+			nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
+		}
+		String str = URLEncodedUtils.format(nvps, charset);
+		
+		return str;
+	 }
+	
+	public static URL buildGetUrl(String strUrl, String query) throws IOException {
+        URL url = new URL(strUrl);
+        if (StringUtils.isEmpty(query)) {
+            return url;
+        }
+
+        if (StringUtils.isEmpty(url.getQuery())) {
+            if (strUrl.endsWith("?")) {
+                strUrl = strUrl + query;
+            } else {
+                strUrl = strUrl + "?" + query;
+            }
+        } else {
+            if (strUrl.endsWith("&")) {
+                strUrl = strUrl + query;
+            } else {
+                strUrl = strUrl + "&" + query;
+            }
+        }
+
+        return new URL(strUrl);
+    }
+
+}

+ 39 - 0
src/main/java/cn/com/sandpay/cashier/sdk/RandomStringGenerator.java

@@ -0,0 +1,39 @@
+/**
+ * Copyright : http://www.sandpay.com.cn , 2011-2014
+ * Project : sandpay-qr-demo
+ * $Id$
+ * $Revision$
+ * Last Changed by pxl at 2018-4-25 下午3:50:48
+ * $URL$
+ * 
+ * Change Log
+ * Author      Change Date    Comments
+ *-------------------------------------------------------------
+ * pxl         2018-4-25        Initailized
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import java.util.Random;
+
+/**
+ *
+ * @ClassName :RandomStringGenerator
+ * @author : pxl
+ * @Date : 2018-4-25 下午3:50:48
+ * @version 2.0.0
+ *
+ */
+public class RandomStringGenerator {
+
+	public static String getRandomStringByLength(int length)
+	  {
+	    String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+	    Random random = new Random();
+	    StringBuffer sb = new StringBuffer();
+	    for (int i = 0; i < length; i++) {
+	      int number = random.nextInt(base.length());
+	      sb.append(base.charAt(number));
+	    }
+	    return sb.toString();
+	  }
+}

+ 278 - 0
src/main/java/cn/com/sandpay/cashier/sdk/SDKConfig.java

@@ -0,0 +1,278 @@
+/**
+ * Licensed Property to Sand Co., Ltd.
+ * <p>
+ * (C) Copyright of Sand Co., Ltd. 2010
+ * All Rights Reserved.
+ * <p>
+ * <p>
+ * Modification History:
+ * =============================================================================
+ * Author           Date           Description
+ * ------------ ---------- ---------------------------------------------------
+ * 企业产品团队       2016-10-12       基本参数工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.Properties;
+
+
+/**
+ * @version 2.0.0
+ * @ClassName: SDKConfig
+ * @Description:
+ */
+public class SDKConfig {
+
+    public static Logger logger = LoggerFactory.getLogger(SDKConfig.class);
+
+    public static final String FILE_NAME = "sand_sdk.properties";
+
+    /**
+     * 通讯地址.
+     */
+    private String url;
+    /**
+     * 商户号.
+     */
+    private String mid;
+    /**
+     * 平台商户号.
+     */
+    private String plMid;
+    /**
+     * 商户私钥证书路径.
+     */
+    private String signCertPath;
+    /**
+     * 商户私钥证书密码.
+     */
+    private String signCertPwd;
+    /**
+     * 杉德证书路径.
+     */
+    private String sandCertPath;
+
+    /**
+     * 配置文件中的通讯地址常量.
+     */
+    public static final String SDK_URL            = "sandsdk.url";
+    /**
+     * 配置文件中的商户号常量.
+     */
+    public static final String SDK_MID            = "sandsdk.mid";
+    /**
+     * 配置文件中的平台商户号常量.
+     */
+    public static final String SDK_PL_MID         = "sandsdk.plMid";
+    /**
+     * 配置文件中的商户私钥证书路径常量.
+     */
+    public static final String SDK_SIGN_CERT_PATH = "sandsdk.signCert.path";
+    /**
+     * 配置文件中的商户私钥证书密码常量.
+     */
+    public static final String SDK_SIGN_CERT_PWD  = "sandsdk.signCert.pwd";
+    /**
+     * 配置文件中的杉德证书路径常量.
+     */
+    public static final String SDK_SNAD_CERT_PATH = "sandsdk.sandCert.path";
+
+    /**
+     * 操作对象.
+     */
+    private static SDKConfig  config = new SDKConfig();
+    /**
+     * 属性文件对象.
+     */
+    private        Properties properties;
+
+    private SDKConfig() {
+        super();
+    }
+
+    /**
+     * 获取config对象.
+     *
+     * @return
+     */
+    public static SDKConfig getConfig() {
+        return config;
+    }
+
+    /**
+     * 从properties文件加载
+     *
+     * @param rootPath 不包含文件名的目录.
+     */
+    public void loadPropertiesFromPath(String rootPath) {
+        if (StringUtils.isNotBlank(rootPath)) {
+            logger.info("从路径读取配置文件: " + rootPath + File.separator + FILE_NAME);
+            File file = new File(rootPath + File.separator + FILE_NAME);
+            InputStream in = null;
+            if (file.exists()) {
+                try {
+                    in = new FileInputStream(file);
+                    properties = new Properties();
+                    properties.load(in);
+                    loadProperties(properties);
+                } catch (FileNotFoundException e) {
+                    logger.error(e.getMessage(), e);
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                } finally {
+                    if (null != in) {
+                        try {
+                            in.close();
+                        } catch (IOException e) {
+                            logger.error(e.getMessage(), e);
+                        }
+                    }
+                }
+            } else {
+                // 由于此时可能还没有完成LOG的加载,因此采用标准输出来打印日志信息
+                logger.error(rootPath + FILE_NAME + "不存在,加载参数失败");
+            }
+        } else {
+            loadPropertiesFromSrc();
+        }
+
+    }
+
+    /**
+     * 从classpath路径下加载配置参数
+     */
+    public void loadPropertiesFromSrc() {
+        InputStream in = null;
+        try {
+            logger.info("从classpath: " + SDKConfig.class.getClassLoader().getResource("").getPath() + " 获取属性文件" + FILE_NAME);
+            in = SDKConfig.class.getClassLoader().getResourceAsStream(FILE_NAME);
+            if (null != in) {
+                properties = new Properties();
+                try {
+                    properties.load(in);
+                } catch (IOException e) {
+                    throw e;
+                }
+            } else {
+                logger.error(FILE_NAME + "属性文件未能在classpath指定的目录下 " + SDKConfig.class.getClassLoader().getResource("").getPath() + " 找到!");
+                return;
+            }
+            loadProperties(properties);
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            if (null != in) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 根据传入的 {@link #load(Properties)}对象设置配置参数
+     *
+     * @param pro
+     */
+    public void loadProperties(Properties pro) {
+        logger.info("开始从属性文件中加载配置项");
+        String value = null;
+
+        value = pro.getProperty(SDK_URL);
+        if (!StringUtils.isEmpty(value)) {
+            this.url = value.trim();
+            logger.info("配置项:通讯地址==>" + SDK_URL + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_MID);
+        if (!StringUtils.isEmpty(value)) {
+            this.mid = value.trim();
+            logger.info("配置项:商户号==>" + SDK_MID + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_PL_MID);
+        if (!StringUtils.isEmpty(value)) {
+            this.plMid = value.trim();
+            logger.info("配置项:平台商户号==>" + SDK_PL_MID + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_SIGN_CERT_PATH);
+        if (!StringUtils.isEmpty(value)) {
+            this.signCertPath = value.trim();
+            logger.info("配置项:商户私钥证书路径==>" + SDK_SIGN_CERT_PATH + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_SIGN_CERT_PWD);
+        if (!StringUtils.isEmpty(value)) {
+            this.signCertPwd = value.trim();
+            logger.info("配置项:商户私钥证书密码==>" + SDK_SIGN_CERT_PWD + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_SNAD_CERT_PATH);
+        if (!StringUtils.isEmpty(value)) {
+            this.sandCertPath = value.trim();
+            logger.info("配置项:杉德公钥证书路径==>" + SDK_SNAD_CERT_PATH + "==>" + value + " 已加载");
+        }
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getMid() {
+        return mid;
+    }
+
+    public void setMid(String mid) {
+        this.mid = mid;
+    }
+
+    public String getPlMid() {
+        return plMid;
+    }
+
+    public void setPlMid(String plMid) {
+        this.plMid = plMid;
+    }
+
+    public String getSignCertPath() {
+        return signCertPath;
+    }
+
+    public void setSignCertPath(String signCertPath) {
+        this.signCertPath = signCertPath;
+    }
+
+    public String getSignCertPwd() {
+        return signCertPwd;
+    }
+
+    public void setSignCertPwd(String signCertPwd) {
+        this.signCertPwd = signCertPwd;
+    }
+
+    public String getSandCertPath() {
+        return sandCertPath;
+    }
+
+    public void setSandCertPath(String sandCertPath) {
+        this.sandCertPath = sandCertPath;
+    }
+
+    public Properties getProperties() {
+        return properties;
+    }
+
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+
+
+}

+ 132 - 0
src/main/java/cn/com/sandpay/cashier/sdk/SDKUtil.java

@@ -0,0 +1,132 @@
+/**
+ *
+ * Licensed Property to Sand Co., Ltd.
+ * 
+ * (C) Copyright of Sand Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author           Date           Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   企业产品团队       2016-10-12       SDK工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @ClassName: SDKUtil
+ * @Description: 
+ * @version 2.0.0
+ */
+public class SDKUtil {
+
+	/**
+	 * 将形如key=value&key=value的字符串转换为相应的Map对象
+	 * 
+	 * @param result
+	 * @return
+	 */
+	public static Map<String, String> convertResultStringToMap(String result) {
+		Map<String, String> map = null;
+		try {
+
+			if (StringUtils.isNotBlank(result)) {
+				if (result.startsWith("{") && result.endsWith("}")) {
+					System.out.println(result.length());
+					result = result.substring(1, result.length() - 1);
+				}
+				map = parseQString(result);
+			}
+
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+		}
+		return map;
+	}
+
+	/**
+	 * 解析应答字符串,生成应答要素
+	 * 
+	 * @param str
+	 *            需要解析的字符串
+	 * @return 解析的结果map
+	 * @throws UnsupportedEncodingException
+	 */
+	public static Map<String, String> parseQString(String str) throws UnsupportedEncodingException {
+
+		Map<String, String> map = new HashMap<String, String>();
+		int len = str.length();
+		StringBuilder temp = new StringBuilder();
+		char curChar;
+		String key = null;
+		boolean isKey = true;
+		boolean isOpen = false;// 值里有嵌套
+		char openName = 0;
+		if (len > 0) {
+			for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
+				curChar = str.charAt(i);// 取当前字符
+				if (isKey) {// 如果当前生成的是key
+
+					if (curChar == '=') {// 如果读取到=分隔符
+						key = temp.toString();
+						temp.setLength(0);
+						isKey = false;
+					} else {
+						temp.append(curChar);
+					}
+				} else {// 如果当前生成的是value
+					if (isOpen) {
+						if (curChar == openName) {
+							isOpen = false;
+						}
+
+					} else {// 如果没开启嵌套
+						if (curChar == '{') {// 如果碰到,就开启嵌套
+							isOpen = true;
+							openName = '}';
+						}
+						if (curChar == '[') {
+							isOpen = true;
+							openName = ']';
+						}
+					}
+					if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域,这时将map里添加
+						putKeyValueToMap(temp, isKey, key, map);
+						temp.setLength(0);
+						isKey = true;
+					} else {
+						temp.append(curChar);
+					}
+				}
+
+			}
+			putKeyValueToMap(temp, isKey, key, map);
+		}
+		return map;
+	}
+
+	private static void putKeyValueToMap(StringBuilder temp, boolean isKey, String key, Map<String, String> map)
+			throws UnsupportedEncodingException {
+		if (isKey) {
+			key = temp.toString();
+			if (key.length() == 0) {
+				throw new RuntimeException("QString format illegal");
+			}
+			map.put(key, "");
+		} else {
+			if (key.length() == 0) {
+				throw new RuntimeException("QString format illegal");
+			}
+			map.put(key, temp.toString());
+		}
+	}
+
+}

+ 25 - 0
src/main/java/com/izouma/nineth/config/SandPayConfig.java

@@ -0,0 +1,25 @@
+package com.izouma.nineth.config;
+
+import cn.com.sandpay.cashier.sdk.CertUtil;
+import lombok.AllArgsConstructor;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+
+@Configuration
+@AllArgsConstructor
+@EnableConfigurationProperties({SandPayProperties.class})
+public class SandPayConfig {
+
+    private final SandPayProperties sandPayProperties;
+
+    @PostConstruct
+    public void init() {
+        try {
+            CertUtil.init(sandPayProperties.getSandCertPath(), sandPayProperties.getSignCertPath(), sandPayProperties.getSignCertPwd());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 16 - 0
src/main/java/com/izouma/nineth/config/SandPayProperties.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "sandpay")
+public class SandPayProperties {
+    private String url = "https://cashier.sandpay.com.cn/qr/api";
+    private String mid;
+    private String plMid;
+    private String signCertPath;
+    private String signCertPwd;
+    private String sandCertPath;
+    private String notifyUrl;
+}

+ 1 - 0
src/main/java/com/izouma/nineth/domain/GiftOrder.java

@@ -3,6 +3,7 @@ package com.izouma.nineth.domain;
 import com.izouma.nineth.annotations.Searchable;
 import com.izouma.nineth.enums.OrderStatus;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;

+ 1 - 0
src/main/java/com/izouma/nineth/domain/MintOrder.java

@@ -4,6 +4,7 @@ import com.izouma.nineth.annotations.Searchable;
 import com.izouma.nineth.converter.MintMaterialListConverter;
 import com.izouma.nineth.enums.MintOrderStatus;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;

+ 1 - 4
src/main/java/com/izouma/nineth/domain/Order.java

@@ -7,10 +7,7 @@ import com.izouma.nineth.annotations.SearchableOne;
 import com.izouma.nineth.converter.FileObjectListConverter;
 import com.izouma.nineth.converter.PrivilegeListConverter;
 import com.izouma.nineth.converter.PropertyListConverter;
-import com.izouma.nineth.enums.CollectionSource;
-import com.izouma.nineth.enums.CollectionType;
-import com.izouma.nineth.enums.OrderStatus;
-import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.*;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;

+ 14 - 0
src/main/java/com/izouma/nineth/dto/airDrop/AirDropDTO.java

@@ -0,0 +1,14 @@
+package com.izouma.nineth.dto.airDrop;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AirDropDTO {
+    @ExcelProperty("phone")
+    private String phone;
+}

+ 2 - 1
src/main/java/com/izouma/nineth/enums/PayMethod.java

@@ -2,7 +2,8 @@ package com.izouma.nineth.enums;
 
 public enum PayMethod {
     WEIXIN("微信"),
-    ALIPAY("支付宝");
+    ALIPAY("支付宝"),
+    SANDPAY("杉德");
 
     private final String description;
 

+ 17 - 0
src/main/java/com/izouma/nineth/enums/SandPayMethod.java

@@ -0,0 +1,17 @@
+package com.izouma.nineth.enums;
+
+public enum SandPayMethod {
+    WEIXIN("微信"),
+    ALIPAY("支付宝"),
+    SANDPAY("杉德");
+
+    private final String description;
+
+    SandPayMethod(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 1 - 0
src/main/java/com/izouma/nineth/event/OrderNotifyEvent.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.event;
 
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;

+ 1 - 1
src/main/java/com/izouma/nineth/listener/MintListener.java

@@ -16,7 +16,7 @@ import org.springframework.stereotype.Service;
         consumerGroup = "${general.mint-group}",
         topic = "${general.mint-topic}",
         consumeMode = ConsumeMode.ORDERLY)
-//@ConditionalOnProperty(value = "general.notify-server", havingValue = "true")
+//@ConditionalOnProperty(value = "general.notify-server", havingValue = "false", matchIfMissing = true)
 public class MintListener implements RocketMQListener<Long> {
     private AssetMintService assetMintService;
 

+ 1 - 0
src/main/java/com/izouma/nineth/repo/ErrorOrder.java

@@ -2,6 +2,7 @@ package com.izouma.nineth.repo;
 
 import com.izouma.nineth.domain.BaseEntity;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;

+ 2 - 0
src/main/java/com/izouma/nineth/repo/OrderRepo.java

@@ -48,6 +48,8 @@ public interface OrderRepo extends JpaRepository<Order, Long>, JpaSpecificationE
 
     List<Order> findAllByPayTimeIsAfter(LocalDateTime payTime);
 
+    List<Order> findAllByPayTimeBetween(LocalDateTime start, LocalDateTime end);
+
     @Query(nativeQuery = true, value = "select user_id, sum(price) total from raex.order_info " +
             "where (status = 'FINISH' or status = 'PROCESSING') " +
             "group by user_id order by sum(price) desc limit 50")

+ 2 - 0
src/main/java/com/izouma/nineth/repo/UserRepo.java

@@ -23,6 +23,8 @@ public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExe
 
     Optional<User> findByUsernameAndDelFalse(String username);
 
+    List<User> findAllByPhoneIn(Collection<String> phones);
+
     List<User> findAllByAuthoritiesContainsAndDelFalse(Authority authority);
 
     Optional<User> findByOpenIdAndDelFalse(String openId);

+ 1 - 0
src/main/java/com/izouma/nineth/service/GiftOrderService.java

@@ -24,6 +24,7 @@ import com.izouma.nineth.domain.User;
 import com.izouma.nineth.enums.AssetStatus;
 import com.izouma.nineth.enums.OrderStatus;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.utils.SnowflakeIdWorker;

+ 1 - 4
src/main/java/com/izouma/nineth/service/MintOrderService.java

@@ -19,10 +19,7 @@ import com.izouma.nineth.config.WxPayProperties;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.dto.PageQuery;
-import com.izouma.nineth.enums.AssetStatus;
-import com.izouma.nineth.enums.MintOrderStatus;
-import com.izouma.nineth.enums.OrderStatus;
-import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.*;
 import com.izouma.nineth.event.OrderNotifyEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;

+ 652 - 0
src/main/java/com/izouma/nineth/service/SandPayService.java

@@ -0,0 +1,652 @@
+package com.izouma.nineth.service;
+
+import cn.com.sandpay.cashier.sdk.*;
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.nineth.config.GeneralProperties;
+import com.izouma.nineth.config.SandPayProperties;
+import com.izouma.nineth.domain.GiftOrder;
+import com.izouma.nineth.domain.MintOrder;
+import com.izouma.nineth.domain.Order;
+import com.izouma.nineth.enums.MintOrderStatus;
+import com.izouma.nineth.enums.OrderStatus;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.GiftOrderRepo;
+import com.izouma.nineth.repo.MintOrderRepo;
+import com.izouma.nineth.repo.OrderRepo;
+import com.izouma.nineth.utils.DateTimeUtils;
+import com.izouma.nineth.utils.SnowflakeIdWorker;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+@Service
+@AllArgsConstructor
+@Slf4j
+public class SandPayService {
+    private final OrderRepo         orderRepo;
+    private final GiftOrderRepo     giftOrderRepo;
+    private final SandPayProperties sandPayProperties;
+    private final MintOrderRepo     mintOrderRepo;
+    private final SnowflakeIdWorker snowflakeIdWorker;
+    private final GeneralProperties generalProperties;
+
+    public String paddingOrderId(String orderId) {
+        if (orderId != null && orderId.length() < 12) {
+            StringBuilder orderIdBuilder = new StringBuilder(orderId);
+            for (int i = orderIdBuilder.length(); i < 12; i++) {
+                orderIdBuilder.insert(0, "0");
+            }
+            orderId = orderIdBuilder.toString();
+        }
+        return orderId;
+    }
+
+    public String getReqTime() {
+        return DateTimeUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss");
+    }
+
+    public static String getTimeout(int seconds) {
+        return DateTimeUtils.format(LocalDateTime.now().plusSeconds(seconds), "yyyyMMddHHmmss");
+    }
+
+    public static String getTimeout(LocalDateTime createTime, int seconds) {
+        return DateTimeUtils.format(Optional.ofNullable(createTime).orElse(LocalDateTime.now())
+                .plusSeconds(seconds), "yyyyMMddHHmmss");
+    }
+
+    public String convertAmount(BigDecimal amount) {
+        DecimalFormat df = new DecimalFormat("000000000000", DecimalFormatSymbols.getInstance(Locale.US));
+        return df.format(amount.multiply(new BigDecimal("100")));
+    }
+
+    public JSONObject requestServer(JSONObject header, JSONObject body, String reqAddr) {
+
+        Map<String, String> reqMap = new HashMap<String, String>();
+        JSONObject reqJson = new JSONObject();
+        reqJson.put("head", header);
+        reqJson.put("body", body);
+        String reqStr = reqJson.toJSONString();
+        String reqSign;
+        // 签名
+        try {
+            reqSign = new String(Base64.encodeBase64(CryptoUtil.digitalSign(reqStr.getBytes(StandardCharsets.UTF_8),
+                    CertUtil.getPrivateKey(), "SHA1WithRSA")));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            return null;
+        }
+        //整体报文格式
+        reqMap.put("charset", "UTF-8");
+        reqMap.put("data", reqStr);
+        reqMap.put("signType", "01");
+        reqMap.put("sign", reqSign);
+        reqMap.put("extend", "");
+
+        String result;
+        try {
+            log.info("请求报文:\n{}", JSONObject.toJSONString(reqJson, true));
+            result = HttpClient.doPost(reqAddr, reqMap, 30000, 30000);
+            result = URLDecoder.decode(result, StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            log.error(e.getMessage());
+            return null;
+        }
+
+        Map<String, String> respMap = SDKUtil.convertResultStringToMap(result);
+        String respData = respMap.get("data");
+        String respSign = respMap.get("sign");
+
+        // 验证签名
+        boolean valid;
+        try {
+            valid = CryptoUtil.verifyDigitalSign(respData.getBytes(StandardCharsets.UTF_8),
+                    Base64.decodeBase64(respSign), CertUtil.getPublicKey(), "SHA1WithRSA");
+            if (!valid) {
+                log.error("verify sign fail.");
+                return null;
+            }
+            log.info("verify sign success");
+            JSONObject respJson = JSONObject.parseObject(respData);
+            if (respJson != null) {
+                log.info("响应码:[{}]", respJson.getJSONObject("head").getString("respCode"));
+                log.info("响应描述:[{}]", respJson.getJSONObject("head").getString("respMsg"));
+                log.info("响应报文:\n{}", JSONObject.toJSONString(respJson, true));
+            } else {
+                log.error("服务器请求异常!!!");
+            }
+            return respJson;
+
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            return null;
+        }
+    }
+
+    public String requestAlipay(String orderId, BigDecimal amount, String subject, String desc,
+                                String timeout, String extend) {
+        JSONObject res = requestAlipayRaw(orderId, amount, subject, desc, timeout, extend);
+        if ("000000".equals(res.getJSONObject("head").getString("respCode"))) {
+            return "alipays://platformapi/startapp?saId=10000007&qrcode=" + res.getJSONObject("body")
+                    .getString("qrCode");
+        }
+        throw new BusinessException("绿洲宇宙冷却系统已启动,请稍后支付");
+    }
+
+    public JSONObject requestAlipayRaw(String orderId, BigDecimal amount, String subject, String desc,
+                                       String timeout, String extend) {
+        if (orderId.length() < 12) {
+            for (int i = orderId.length(); i < 12; i++) {
+                orderId = "0" + orderId;
+            }
+        }
+        JSONObject header = new JSONObject();
+        header.put("version", "1.0");                      //版本号
+        header.put("method", "sandpay.trade.precreate");   //接口名称:统一下单并支付
+        header.put("productId", "00000006");               //产品编码
+        header.put("mid", sandPayProperties.getMid());     //商户号
+        header.put("accessType", "1");                     //接入类型设置为普通商户接入
+        header.put("channelType", "07");                   //渠道类型:07-互联网   08-移动端
+        header.put("reqTime", getReqTime());               //请求时间
+
+        JSONObject body = new JSONObject();
+        body.put("payTool", "0401");                                  //支付工具: 固定填写0401
+        body.put("orderCode", orderId);                               //商户订单号
+        body.put("totalAmount", convertAmount(amount));               //订单金额 12位长度,精确到分
+        //body.put("limitPay", "5");                                  //限定支付方式 送1-限定不能使用贷记卡	送4-限定不能使用花呗	送5-限定不能使用贷记卡+花呗
+        body.put("subject", subject);                                 //订单标题
+        body.put("body", desc);                                       //订单描述
+        body.put("txnTimeOut", timeout);                              //订单超时时间
+        body.put("notifyUrl", sandPayProperties.getNotifyUrl());      //异步通知地址
+        body.put("bizExtendParams", "");                              //业务扩展参数
+        body.put("merchExtendParams", "");                            //商户扩展参数
+        body.put("extend", extend);                                   //扩展域
+
+        return requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/create");
+    }
+
+    public JSONObject requestQuick(String orderId, BigDecimal amount, String subject, String desc,
+                                   int timeout, String extend, String frontUrl) {
+        if (orderId.length() < 12) {
+            for (int i = orderId.length(); i < 12; i++) {
+                orderId = "0" + orderId;
+            }
+        }
+        JSONObject header = new JSONObject();
+        header.put("version", "1.0");                         //版本号
+        header.put("method", "sandpay.trade.pay");            //接口名称:统一下单
+        header.put("mid", sandPayProperties.getMid());        //商户号
+        header.put("accessType", "1");                        //接入类型设置为平台商户接入		//接入类型设置为普通商户接入
+        header.put("channelType", "07");                      //渠道类型:07-互联网   08-移动端
+        header.put("reqTime", getReqTime());                  //请求时间
+        header.put("productId", "00000008");                  //产品编码
+
+        JSONObject body = new JSONObject();
+        body.put("orderCode", orderId);                                           //商户订单号
+        body.put("totalAmount", convertAmount(amount));                           //订单金额
+        body.put("subject", subject);                                             //订单标题
+        body.put("body", desc);                                                   //订单描述
+        body.put("txnTimeOut", getTimeout(timeout));                              //订单超时时间
+        body.put("clientIp", "192.168.22.55");                                    //客户端IP
+        body.put("limitPay", "");                                                 //限定支付方式	送1-限定不能使用贷记卡送	4-限定不能使用花呗	送5-限定不能使用贷记卡+花呗
+        body.put("notifyUrl", sandPayProperties.getNotifyUrl());                  //异步通知地址
+        body.put("frontUrl", frontUrl);                                           //前台通知地址
+        body.put("storeId", "");                                                  //商户门店编号
+        body.put("terminalId", "");                                               //商户终端编号
+        body.put("operatorId", "");                                               //操作员编号
+        body.put("clearCycle", "");                                               //清算模式
+        body.put("royaltyInfo", "");                                              //分账信息
+        body.put("riskRateInfo", "");                                             //风控信息域
+        body.put("bizExtendParams", "");                                          //业务扩展参数
+        body.put("merchExtendParams", "");                                        //商户扩展参数
+        body.put("extend", extend);                                               //扩展域
+        body.put("payMode", "sand_h5");                                           //支付模式
+
+        return requestServer(header, body, "https://cashier.sandpay.com.cn/gateway/api/order/pay");
+    }
+
+    public JSONObject query(String orderId) {
+        JSONObject header = new JSONObject();
+        header.put("version", "1.0");                     //版本号
+        header.put("method", "sandpay.trade.query");      //接口名称:订单查询
+        header.put("productId", "00000006");              //产品编码
+        header.put("mid", sandPayProperties.getMid());    //商户号
+        header.put("accessType", "1");                    //接入类型设置为普通商户接入
+        header.put("channelType", "07");                  //渠道类型:07-互联网   08-移动端
+        header.put("reqTime", getReqTime());              //请求时间
+
+        JSONObject body = new JSONObject();
+        body.put("orderCode", orderId);
+        body.put("extend", "");                            //扩展域
+
+        return requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/query");
+    }
+
+    public JSONObject refund(String orderId, BigDecimal amount) {
+        JSONObject header = new JSONObject();
+        header.put("version", "1.0");                           //版本号
+        header.put("method", "sandpay.trade.refund");           //接口名称:退货
+        header.put("productId", "00000006");                    //产品编码
+        header.put("mid", sandPayProperties.getMid());          //商户号
+        header.put("accessType", "1");                          //接入类型设置为普通商户接入
+        header.put("channelType", "07");                        //渠道类型:07-互联网   08-移动端
+        header.put("reqTime", getReqTime());                    //请求时间
+
+        JSONObject body = new JSONObject();
+        body.put("orderCode", snowflakeIdWorker.nextId());        //商户订单号
+        body.put("oriOrderCode", paddingOrderId(orderId));        //原交易订单号
+        body.put("refundAmount", convertAmount(amount));          //退货金额
+        body.put("refundReason", "退款");                      //退货原因
+        body.put("notifyUrl", sandPayProperties.getNotifyUrl());  //异步通知地址
+        body.put("extend", "");
+
+        return requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/refund");
+    }
+
+    @Cacheable(value = "sandPay", key = "#orderId")
+    public String payOrder(Long orderId) {
+        Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+        if (order.getStatus() != OrderStatus.NOT_PAID) {
+            throw new BusinessException("订单状态错误");
+        }
+        JSONObject extend = new JSONObject();
+        extend.put("type", "order");
+        extend.put("id", orderId);
+
+        JSONObject res = requestAlipayRaw(orderId.toString(), order.getTotalPrice(), order.getName(), order.getName(),
+                getTimeout(order.getCreatedAt(), 180), extend.toJSONString());
+        if (res == null)
+            throw new BusinessException("下单失败,请稍后再试");
+
+        if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
+            String msg = res.getJSONObject("head").getString("respMsg");
+            if (msg.contains("超限")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            if (msg.contains("商户状态")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            throw new BusinessException(msg);
+        }
+
+        return res.getJSONObject("body").getString("qrCode");
+    }
+
+    @Cacheable(value = "sandPayQuick", key = "#orderId")
+    public String payOrderQuick(Long orderId) {
+        Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+        if (order.getStatus() != OrderStatus.NOT_PAID) {
+            throw new BusinessException("订单状态错误");
+        }
+        JSONObject extend = new JSONObject();
+        extend.put("type", "order");
+        extend.put("id", orderId);
+
+        JSONObject res = requestQuick(orderId.toString(), order.getTotalPrice(), order.getName(), order.getName(),
+                180, extend.toJSONString(), generalProperties.getHost() + "/9th/orderDetail?id=" + orderId);
+        if (res == null)
+            throw new BusinessException("下单失败,请稍后再试");
+
+        if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
+            String msg = res.getJSONObject("head").getString("respMsg");
+            if (msg.contains("超限")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            if (msg.contains("商户状态")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            throw new BusinessException(msg);
+        }
+        return res.getJSONObject("body").getString("credential");
+    }
+
+    @Cacheable(value = "sandPayQuick", key = "#orderId")
+    public String payGiftQuick(Long orderId) {
+        GiftOrder order = giftOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+        if (order.getStatus() != OrderStatus.NOT_PAID) {
+            throw new BusinessException("订单状态错误");
+        }
+        JSONObject extend = new JSONObject();
+        extend.put("type", "gift");
+        extend.put("id", orderId);
+
+        JSONObject res = requestQuick(orderId.toString(), order.getGasPrice(), "转增" + order.getAssetId(),
+                "转增" + order.getAssetId(), 180, extend.toJSONString(),
+                generalProperties.getHost() + "/9th/");
+        if (res == null)
+            throw new BusinessException("下单失败,请稍后再试");
+
+        if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
+            String msg = res.getJSONObject("head").getString("respMsg");
+            if (msg.contains("超限")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            if (msg.contains("商户状态")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            throw new BusinessException(msg);
+        }
+        return res.getJSONObject("body").getString("credential");
+    }
+
+    @Cacheable(value = "sandPayQuick", key = "#orderId")
+    public String payMintQuick(Long orderId) {
+        MintOrder order = mintOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+        if (order.getStatus() != MintOrderStatus.NOT_PAID) {
+            throw new BusinessException("订单状态错误");
+        }
+        JSONObject extend = new JSONObject();
+        extend.put("type", "mintOrder");
+        extend.put("id", orderId);
+
+        JSONObject res = requestQuick(orderId.toString(), order.getGasPrice(),
+                "铸造活动:" + order.getMintActivityId(), "铸造活动:" + order.getMintActivityId(),
+                180, extend.toJSONString(), generalProperties.getHost() + "/9th/");
+        if (res == null)
+            throw new BusinessException("下单失败,请稍后再试");
+
+        if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
+            String msg = res.getJSONObject("head").getString("respMsg");
+            if (msg.contains("超限")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            if (msg.contains("商户状态")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            throw new BusinessException(msg);
+        }
+        return res.getJSONObject("body").getString("credential");
+    }
+
+    @Cacheable(value = "sandPay", key = "#orderId")
+    public String payGiftOrder(Long orderId) {
+        GiftOrder order = giftOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+        if (order.getStatus() != OrderStatus.NOT_PAID) {
+            throw new BusinessException("订单状态错误");
+        }
+        JSONObject extend = new JSONObject();
+        extend.put("type", "gift");
+        extend.put("id", orderId);
+
+        JSONObject res = requestAlipayRaw(orderId.toString(), order.getGasPrice(), "转增" + order.getAssetId(),
+                "转增" + order.getAssetId(),
+                getTimeout(order.getCreatedAt(), 180), extend.toJSONString());
+        if (res == null)
+            throw new BusinessException("下单失败,请稍后再试");
+
+        if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
+            String msg = res.getJSONObject("head").getString("respMsg");
+            if (msg.contains("超限")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            if (msg.contains("商户状态")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            throw new BusinessException(msg);
+        }
+
+        return res.getJSONObject("body").getString("qrCode");
+    }
+
+    @Cacheable(value = "sandPay", key = "#orderId")
+    public String payMintOrder(Long orderId) {
+        MintOrder order = mintOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+        if (order.getStatus() != MintOrderStatus.NOT_PAID) {
+            throw new BusinessException("订单状态错误");
+        }
+        JSONObject extend = new JSONObject();
+        extend.put("type", "mintOrder");
+        extend.put("id", orderId);
+
+        JSONObject res = requestAlipayRaw(orderId.toString(), order.getGasPrice(), "铸造活动:" + order.getMintActivityId(),
+                "铸造活动:" + order.getMintActivityId(), getTimeout(order.getCreatedAt(), 180), extend.toJSONString());
+        if (res == null)
+            throw new BusinessException("下单失败,请稍后再试");
+
+        if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
+            String msg = res.getJSONObject("head").getString("respMsg");
+            if (msg.contains("超限")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            if (msg.contains("商户状态")) {
+                throw new BusinessException("超过商户单日额度");
+            }
+            throw new BusinessException(msg);
+        }
+
+        return res.getJSONObject("body").getString("qrCode");
+    }
+
+
+
+    public JSONObject transfer(String id, String name, String bank, BigDecimal amount) {
+        JSONObject request = new JSONObject();
+        DecimalFormat df = new DecimalFormat("000000000000", DecimalFormatSymbols.getInstance(Locale.US));
+        request.put("version", "01");                          //版本号
+        request.put("productId", "00000004");                  //产品ID
+        request.put("tranTime", getReqTime());                 //交易时间
+        request.put("orderCode", id);  //订单号
+        request.put("timeOut", getTimeout(180));       //订单超时时间
+        request.put("tranAmt", df.format(amount.multiply(new BigDecimal("100"))));      //金额
+        request.put("currencyCode", "156");                    //币种
+        request.put("accAttr", "0");                           //账户属性     0-对私   1-对公
+        request.put("accType", "4");                           //账号类型      3-公司账户  4-银行卡
+        request.put("accNo", bank);                            //收款人账户号
+        request.put("accName", name);                          //收款人账户名
+        request.put("provNo", "");                             //收款人开户省份编码
+        request.put("cityNo", "");                             //收款人开会城市编码
+        request.put("bankName", "");                           //收款账户开户行名称
+        request.put("bankType", "");                           //收款人账户联行号
+        request.put("remark", "消费");                          //摘要
+        request.put("payMode", "");                            //付款模式
+        request.put("channelType", "");                        //渠道类型
+        request.put("extendParams", "");                       //业务扩展参数
+        request.put("reqReserved", "");                        //请求方保留域
+        request.put("extend", "");                             //扩展域
+        request.put("phone", "");                              //手机号
+
+        String reqData = request.toJSONString();
+        log.info("请求数据:{}", reqData);
+
+        try {
+
+            String aesKey = RandomStringGenerator.getRandomStringByLength(16);
+            byte[] aesKeyBytes = aesKey.getBytes(StandardCharsets.UTF_8);
+
+            byte[] plainBytes = reqData.getBytes(StandardCharsets.UTF_8);
+            String encryptData = new String(Base64.encodeBase64(
+                    CryptoUtil.AESEncrypt(plainBytes, aesKeyBytes, "AES",
+                            "AES/ECB/PKCS5Padding", null)), StandardCharsets.UTF_8);
+
+            String sign = new String(Base64.encodeBase64(
+                    CryptoUtil.digitalSign(plainBytes, CertUtil.getPrivateKey(),
+                            "SHA1WithRSA")), StandardCharsets.UTF_8);
+
+            String encryptKey = new String(Base64.encodeBase64(
+                    CryptoUtil.RSAEncrypt(aesKeyBytes, CertUtil.getPublicKey(), 2048, 11,
+                            "RSA/ECB/PKCS1Padding")), StandardCharsets.UTF_8);
+
+
+            Map<String, String> reqMap = new HashMap<String, String>();
+            //整体报文格式
+            reqMap.put("transCode", "RTPM"); // 交易码
+            reqMap.put("accessType", "0"); // 接入类型
+            reqMap.put("merId", sandPayProperties.getMid()); // 合作商户ID	杉德系统分配,唯一标识
+            reqMap.put("encryptKey", encryptKey); // 加密后的AES秘钥
+            reqMap.put("encryptData", encryptData); // 加密后的请求/应答报文
+            reqMap.put("sign", sign); // 签名
+            reqMap.put("extend", ""); // 扩展域
+
+            String result;
+            try {
+                log.info("请求报文:{}", reqMap);
+                result = HttpClient.doPost("https://caspay.sandpay.com.cn/agent-main/openapi/agentpay",
+                        reqMap, 300000, 300000);
+                result = URLDecoder.decode(result, StandardCharsets.UTF_8);
+            } catch (IOException e) {
+                log.error(e.getMessage());
+                return null;
+            }
+
+            log.info("响应报文:{}", result);
+            Map<String, String> responseMap = SDKUtil.convertResultStringToMap(result);
+
+            String retEncryptKey = responseMap.get("encryptKey");
+            String retEncryptData = responseMap.get("encryptData");
+            String retSign = responseMap.get("sign");
+
+            log.debug("retEncryptKey:[{}]", retEncryptKey);
+            log.debug("retEncryptData:[{}]", retEncryptData);
+            log.debug("retSign:[{}]", retSign);
+
+            byte[] decodeBase64KeyBytes = Base64.decodeBase64(retEncryptKey
+                    .getBytes(StandardCharsets.UTF_8));
+
+            byte[] merchantAESKeyBytes = CryptoUtil.RSADecrypt(
+                    decodeBase64KeyBytes, CertUtil.getPrivateKey(), 2048, 11,
+                    "RSA/ECB/PKCS1Padding");
+
+            byte[] decodeBase64DataBytes = Base64.decodeBase64(retEncryptData.getBytes(StandardCharsets.UTF_8));
+
+            byte[] respDataBytes = CryptoUtil.AESDecrypt(decodeBase64DataBytes,
+                    merchantAESKeyBytes, "AES", "AES/ECB/PKCS5Padding", null);
+
+            String respData = new String(respDataBytes, StandardCharsets.UTF_8);
+            log.info("retData:[" + respData + "]");
+            System.out.println("响应data数据:" + respData);
+
+            byte[] signBytes = Base64.decodeBase64(retSign.getBytes(StandardCharsets.UTF_8));
+
+            boolean isValid = CryptoUtil.verifyDigitalSign(respDataBytes, signBytes,
+                    CertUtil.getPublicKey(), "SHA1WithRSA");
+
+            if (!isValid) {
+                log.error("verify sign fail.");
+                return null;
+            }
+            log.info("verify sign success");
+            System.out.println("verify sign success");
+
+            JSONObject respJson = JSONObject.parseObject(respData);
+            return respJson;
+
+        } catch (Exception e) {
+
+            log.error(e.getMessage());
+            return null;
+        }
+    }
+
+    public JSONObject queryTransfer(String tranTime, String orderId) {
+        JSONObject request = new JSONObject();
+        request.put("version", "01");          // 版本号
+        request.put("productId", "00000004");  // 产品ID
+        request.put("tranTime", tranTime);     // 查询订单的交易时间
+        request.put("orderCode", orderId);     // 要查询的订单号
+
+        String reqData = request.toJSONString();
+        log.info("请求数据:{}", reqData);
+
+        try {
+
+            String aesKey = RandomStringGenerator.getRandomStringByLength(16);
+            byte[] aesKeyBytes = aesKey.getBytes(StandardCharsets.UTF_8);
+
+            byte[] plainBytes = reqData.getBytes(StandardCharsets.UTF_8);
+            String encryptData = new String(Base64.encodeBase64(
+                    CryptoUtil.AESEncrypt(plainBytes, aesKeyBytes, "AES",
+                            "AES/ECB/PKCS5Padding", null)), StandardCharsets.UTF_8);
+
+            String sign = new String(Base64.encodeBase64(
+                    CryptoUtil.digitalSign(plainBytes, CertUtil.getPrivateKey(),
+                            "SHA1WithRSA")), StandardCharsets.UTF_8);
+
+            String encryptKey = new String(Base64.encodeBase64(
+                    CryptoUtil.RSAEncrypt(aesKeyBytes, CertUtil.getPublicKey(), 2048, 11,
+                            "RSA/ECB/PKCS1Padding")), StandardCharsets.UTF_8);
+
+
+            Map<String, String> reqMap = new HashMap<String, String>();
+            //整体报文格式
+            reqMap.put("transCode", "ODQU"); // 交易码
+            reqMap.put("accessType", "0"); // 接入类型
+            reqMap.put("merId", sandPayProperties.getMid()); // 合作商户ID	杉德系统分配,唯一标识
+            reqMap.put("plId", null);
+            reqMap.put("encryptKey", encryptKey); // 加密后的AES秘钥
+            reqMap.put("encryptData", encryptData); // 加密后的请求/应答报文
+            reqMap.put("sign", sign); // 签名
+            reqMap.put("extend", ""); // 扩展域
+
+            String result;
+            try {
+                log.info("请求报文:{}", reqMap);
+                result = HttpClient.doPost("https://caspay.sandpay.com.cn/agent-main/openapi/queryOrder",
+                        reqMap, 300000, 300000);
+                result = URLDecoder.decode(result, StandardCharsets.UTF_8);
+            } catch (IOException e) {
+                log.error(e.getMessage());
+                return null;
+            }
+
+            log.info("响应报文:{}", result);
+            Map<String, String> responseMap = SDKUtil.convertResultStringToMap(result);
+
+            String retEncryptKey = responseMap.get("encryptKey");
+            String retEncryptData = responseMap.get("encryptData");
+            String retSign = responseMap.get("sign");
+
+            log.debug("retEncryptKey:[{}]", retEncryptKey);
+            log.debug("retEncryptData:[{}]", retEncryptData);
+            log.debug("retSign:[{}]", retSign);
+
+            byte[] decodeBase64KeyBytes = Base64.decodeBase64(retEncryptKey
+                    .getBytes(StandardCharsets.UTF_8));
+
+            byte[] merchantAESKeyBytes = CryptoUtil.RSADecrypt(
+                    decodeBase64KeyBytes, CertUtil.getPrivateKey(), 2048, 11,
+                    "RSA/ECB/PKCS1Padding");
+
+            byte[] decodeBase64DataBytes = Base64.decodeBase64(retEncryptData.getBytes(StandardCharsets.UTF_8));
+
+            byte[] respDataBytes = CryptoUtil.AESDecrypt(decodeBase64DataBytes,
+                    merchantAESKeyBytes, "AES", "AES/ECB/PKCS5Padding", null);
+
+            String respData = new String(respDataBytes, StandardCharsets.UTF_8);
+            log.info("retData:[" + respData + "]");
+            System.out.println("响应data数据:" + respData);
+
+            byte[] signBytes = Base64.decodeBase64(retSign.getBytes(StandardCharsets.UTF_8));
+
+            boolean isValid = CryptoUtil.verifyDigitalSign(respDataBytes, signBytes,
+                    CertUtil.getPublicKey(), "SHA1WithRSA");
+
+            if (!isValid) {
+                log.error("verify sign fail.");
+                return null;
+            }
+            log.info("verify sign success");
+            System.out.println("verify sign success");
+
+            JSONObject respJson = JSONObject.parseObject(respData);
+            return respJson;
+
+        } catch (Exception e) {
+
+            log.error(e.getMessage());
+            return null;
+        }
+    }
+}

+ 12 - 8
src/main/java/com/izouma/nineth/service/UserService.java

@@ -82,6 +82,7 @@ public class UserService {
     private GeneralProperties             generalProperties;
     private RedisTemplate<String, Object> redisTemplate;
     private PasswordEncoder               passwordEncoder;
+    private AssetRepo                     assetRepo;
 
     public User update(User user) {
         if (!SecurityUtils.hasRole(AuthorityName.ROLE_ADMIN)) {
@@ -128,7 +129,8 @@ public class UserService {
                 if (roleName.equals("ROLE_MINTER")) {
                     and.add(criteriaBuilder.equal(root.get("minter"), true));
                 } else {
-                    and.add(criteriaBuilder.isMember(Authority.get(AuthorityName.valueOf(roleName)), root.get("authorities")));
+                    and.add(criteriaBuilder
+                            .isMember(Authority.get(AuthorityName.valueOf(roleName)), root.get("authorities")));
                 }
             }
 
@@ -522,7 +524,7 @@ public class UserService {
             throw new BusinessException("用户不存在或未认证");
         }
         String realName = identityAuthRepo.findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(
-                        user.getId(), AuthStatus.SUCCESS)
+                user.getId(), AuthStatus.SUCCESS)
                 .map(IdentityAuth::getRealName).orElse("").replaceAll(".*(?=.)", "**");
         Map<String, Object> map = new HashMap<>();
         map.put("id", user.getId());
@@ -535,8 +537,8 @@ public class UserService {
 
     public Map<String, Object> searchByPhoneAdmin(String phoneStr) {
         List<String> phone = Arrays.stream(phoneStr.replaceAll("\n", " ")
-                        .replaceAll("\r\n", " ")
-                        .split(" "))
+                .replaceAll("\r\n", " ")
+                .split(" "))
                 .map(String::trim)
                 .filter(s -> !StringUtils.isEmpty(s))
                 .collect(Collectors.toList());
@@ -551,7 +553,8 @@ public class UserService {
 
     public void addBankCard(Long userId, String bankNo, String phone, String code) throws BaseAdaPayException {
         User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
-        IdentityAuth identityAuth = identityAuthRepo.findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(userId, AuthStatus.SUCCESS)
+        IdentityAuth identityAuth = identityAuthRepo
+                .findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(userId, AuthStatus.SUCCESS)
                 .orElseThrow(new BusinessException("用户未认证"));
         if (identityAuth.isOrg()) {
             //throw new BusinessException("企业认证用户请绑定对公账户");
@@ -685,7 +688,8 @@ public class UserService {
         list.forEach(user -> {
             try {
                 Thread.sleep(500);
-                IdentityAuth identityAuth = identityAuthRepo.findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(user.getId(), AuthStatus.SUCCESS)
+                IdentityAuth identityAuth = identityAuthRepo
+                        .findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(user.getId(), AuthStatus.SUCCESS)
                         .orElseThrow(new BusinessException("用户未认证"));
                 UserBankCard userBankCard = userBankCardRepo.findByUserId(user.getId()).stream().findAny()
                         .orElseThrow(new BusinessException("未绑卡"));
@@ -715,9 +719,9 @@ public class UserService {
         user.setPassword(null);
         user.setTradeCode(null);
         List<UserBankCard> byUserId = userBankCardRepo.findByUserId(user.getId());
-        if (byUserId.size()==0||byUserId==null){
+        if (byUserId.size() == 0 || byUserId == null) {
             user.setIsUserBankCard(false);
-        }else {
+        } else {
             user.setIsUserBankCard(true);
         }
         return user;

+ 1 - 0
src/main/java/com/izouma/nineth/utils/excel/PayMethodConverter.java

@@ -6,6 +6,7 @@ import com.alibaba.excel.metadata.CellData;
 import com.alibaba.excel.metadata.GlobalConfiguration;
 import com.alibaba.excel.metadata.property.ExcelContentProperty;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 
 public class PayMethodConverter implements Converter<PayMethod> {
     @Override

+ 29 - 0
src/main/java/com/izouma/nineth/utils/excel/UploadDataListener.java

@@ -0,0 +1,29 @@
+package com.izouma.nineth.utils.excel;
+
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UploadDataListener<T> extends AnalysisEventListener<T> {
+    private final List<T> data;
+
+    public UploadDataListener() {
+        this.data = new ArrayList<>();
+    }
+
+    public List<T> getData() {
+        return data;
+    }
+
+    @Override
+    public void invoke(T t, AnalysisContext analysisContext) {
+        data.add(t);
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+
+    }
+}

+ 1 - 0
src/main/java/com/izouma/nineth/web/OrderController.java

@@ -7,6 +7,7 @@ import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.enums.CollectionType;
 import com.izouma.nineth.enums.OrderStatus;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.OrderRepo;
 import com.izouma.nineth.repo.UserRepo;

+ 1 - 0
src/main/java/com/izouma/nineth/web/OrderNotifyController.java

@@ -16,6 +16,7 @@ import com.izouma.nineth.config.AlipayProperties;
 import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.config.RedisKeys;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 import com.izouma.nineth.event.OrderNotifyEvent;
 import com.izouma.nineth.repo.ErrorOrderRepo;
 import com.izouma.nineth.service.AssetService;

+ 38 - 14
src/main/java/com/izouma/nineth/web/OrderPayController.java

@@ -9,10 +9,8 @@ import com.izouma.nineth.domain.Order;
 import com.izouma.nineth.enums.OrderStatus;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.OrderRepo;
-import com.izouma.nineth.service.AssetService;
-import com.izouma.nineth.service.GiftOrderService;
-import com.izouma.nineth.service.MintOrderService;
-import com.izouma.nineth.service.OrderService;
+import com.izouma.nineth.service.*;
+import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -37,20 +35,32 @@ public class OrderPayController {
     private final GiftOrderService giftOrderService;
     private final OrderRepo        orderRepo;
     private final MintOrderService mintOrderService;
+    private final SandPayService   sandPayService;
 
     @RequestMapping(value = "/alipay_h5", method = RequestMethod.GET)
     @ResponseBody
     public String payOrderAlipayH5(Long id, Model model) throws BaseAdaPayException {
-        return (String) orderService.payAdapay(id, "alipay_wap", null);
+//        return (String) orderService.payAdapay(id, "alipay_wap", null);
+        return sandPayService.payOrder(id);
     }
 
     @RequestMapping(value = "/alipay_wx", method = RequestMethod.GET)
     public String payOrderAlipayWx(Long id, Model model) throws BaseAdaPayException {
+//        Order order = orderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));
+//        if (order.getStatus() != OrderStatus.NOT_PAID) {
+//            return "redirect:/9th/store";
+//        }
+//        String payUrl = (String) orderService.payAdapay(id, "alipay_wap", null);
+//        model.addAttribute("payUrl", payUrl);
+//        model.addAttribute("orderId", id);
+//        return "AlipayHtml";
+
+
         Order order = orderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));
         if (order.getStatus() != OrderStatus.NOT_PAID) {
             return "redirect:/9th/store";
         }
-        String payUrl = (String) orderService.payAdapay(id, "alipay_wap", null);
+        String payUrl = sandPayService.payOrder(id);
         model.addAttribute("payUrl", payUrl);
         model.addAttribute("orderId", id);
         return "AlipayHtml";
@@ -65,9 +75,18 @@ public class OrderPayController {
     @RequestMapping(value = "/alipay_app", method = RequestMethod.GET)
     @ResponseBody
     public String payOrderAlipayApp(Long id, Model model) throws BaseAdaPayException {
-        return (String) orderService.payAdapay(id, "alipay", null);
+        return sandPayService.payOrder(id);
+    }
+
+    @ApiOperation("衫德h5快捷")
+    @RequestMapping(value = "/sandQuick", method = RequestMethod.GET, produces = "text/html")
+    @ResponseBody
+    public String sandQuick(@RequestParam Long id, Model model) throws BaseAdaPayException {
+//        return (String) orderService.payAdapay(id, "alipay_wap", null);
+        return sandPayService.payOrderQuick(id);
     }
 
+
     @RequestMapping(value = "/weixin_h5")
     public String payOrderWeixinH5(Long id, Model model, @RequestHeader(value = "User-Agent") String userAgent) throws EncoderException, WxPayException {
         detectUA(userAgent, model);
@@ -85,7 +104,8 @@ public class OrderPayController {
     public String payOrderWeixinPC(@RequestParam Long id, @RequestParam String code, Model model) throws WxPayException, EncoderException, WxErrorException {
         WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
         WxMpUser user = wxMpService.oauth2getUserInfo(accessToken, null);
-        WxPayMpOrderResult payParams = (WxPayMpOrderResult) orderService.payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, user.getOpenId());
+        WxPayMpOrderResult payParams = (WxPayMpOrderResult) orderService
+                .payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, user.getOpenId());
         model.addAttribute("payParams", JSON.toJSONString(payParams));
         return "PayOrderPC";
     }
@@ -93,12 +113,14 @@ public class OrderPayController {
     @RequestMapping(value = "/gift/alipay_h5", method = RequestMethod.GET)
     @ResponseBody
     public String payGiftOrderAlipayH5(Long id, Model model) throws BaseAdaPayException {
-        return (String) giftOrderService.payAdapay(id, "alipay_wap", null);
+//        return (String) giftOrderService.payAdapay(id, "alipay_wap", null);
+        return sandPayService.payGiftOrder(id);
     }
 
     @RequestMapping(value = "/gift/alipay_wx", method = RequestMethod.GET)
     public String payGiftOrderAlipayWx(Long id, Model model) throws BaseAdaPayException {
-        String payUrl = (String) giftOrderService.payAdapay(id, "alipay_wap", null);
+//        String payUrl = (String) giftOrderService.payAdapay(id, "alipay_wap", null);
+        String payUrl = sandPayService.payGiftOrder(id);
         model.addAttribute("payUrl", payUrl);
         model.addAttribute("orderId", id);
         return "AlipayHtml";
@@ -107,13 +129,13 @@ public class OrderPayController {
     @RequestMapping(value = "/gift/alipay_qr", method = RequestMethod.GET)
     @ResponseBody
     public String payGiftOrderAlipayQR(Long id, Model model) throws BaseAdaPayException {
-        return (String) giftOrderService.payAdapay(id, "alipay_qr", null);
+        return sandPayService.payGiftOrder(id);
     }
 
     @RequestMapping(value = "/gift/alipay_app", method = RequestMethod.GET)
     @ResponseBody
     public String payGiftOrderAlipayApp(Long id, Model model) throws BaseAdaPayException {
-        return (String) giftOrderService.payAdapay(id, "alipay", null);
+        return sandPayService.payGiftOrder(id);
     }
 
     @RequestMapping(value = "/gift/weixin_h5")
@@ -133,7 +155,8 @@ public class OrderPayController {
     public String payGiftOrderWeixinPC(@RequestParam Long id, @RequestParam String code, Model model) throws WxPayException, EncoderException, WxErrorException {
         WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
         WxMpUser user = wxMpService.oauth2getUserInfo(accessToken, null);
-        WxPayMpOrderResult payParams = (WxPayMpOrderResult) giftOrderService.payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, user.getOpenId());
+        WxPayMpOrderResult payParams = (WxPayMpOrderResult) giftOrderService
+                .payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, user.getOpenId());
         model.addAttribute("payParams", JSON.toJSONString(payParams));
         return "PayOrderPC";
     }
@@ -181,7 +204,8 @@ public class OrderPayController {
     public String payMintOrderWeixinPC(@RequestParam Long id, @RequestParam String code, Model model) throws WxPayException, EncoderException, WxErrorException {
         WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
         WxMpUser user = wxMpService.oauth2getUserInfo(accessToken, null);
-        WxPayMpOrderResult payParams = (WxPayMpOrderResult) mintOrderService.payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, user.getOpenId());
+        WxPayMpOrderResult payParams = (WxPayMpOrderResult) mintOrderService
+                .payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, user.getOpenId());
         model.addAttribute("payParams", JSON.toJSONString(payParams));
         return "PayOrderPC";
     }

+ 87 - 0
src/main/java/com/izouma/nineth/web/SandPayController.java

@@ -0,0 +1,87 @@
+package com.izouma.nineth.web;
+
+import cn.com.sandpay.cashier.sdk.CertUtil;
+import cn.com.sandpay.cashier.sdk.CryptoUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.nineth.config.GeneralProperties;
+import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.event.OrderNotifyEvent;
+import com.izouma.nineth.service.*;
+import com.izouma.nineth.utils.SnowflakeIdWorker;
+import com.izouma.nineth.enums.SandPayMethod;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.nio.charset.StandardCharsets;
+
+@RestController
+@RequestMapping("/sandpay")
+@Slf4j
+@AllArgsConstructor
+public class SandPayController {
+
+    private SandPayService     sandPayService;
+    private SnowflakeIdWorker  snowflakeIdWorker;
+    private GeneralProperties  generalProperties;
+    private RocketMQTemplate   rocketMQTemplate;
+    private GiftOrderService   giftOrderService;
+    private MintOrderService   mintOrderService;
+
+    @PostMapping("/notify")
+    public Object notifyOrder(HttpServletRequest req, HttpServletResponse resp) {
+        String data = req.getParameter("data");
+        String sign = req.getParameter("sign");
+        // 验证签名
+        boolean valid;
+        try {
+            valid = CryptoUtil.verifyDigitalSign(data.getBytes(StandardCharsets.UTF_8), Base64.decodeBase64(sign),
+                    CertUtil.getPublicKey(), "SHA1WithRSA");
+            if (!valid) {
+                log.error("verify sign fail.");
+                log.error("签名字符串(data)为:" + data);
+                log.error("签名值(sign)为:" + sign);
+            } else {
+                log.info("verify sign success");
+                JSONObject dataJson = JSONObject.parseObject(data);
+                if (dataJson != null) {
+                    log.info("通知业务数据为:" + JSONObject.toJSONString(dataJson, true));
+                    if ("000000".equals(dataJson.getJSONObject("head").getString("respCode"))) {
+                        JSONObject body = dataJson.getJSONObject("body");
+                        JSONObject extend = body.getJSONObject("extend");
+                        String type = extend.getString("type");
+                        Long id = extend.getLong("id");
+                        String payOrderCode = body.getString("payOrderCode");
+                        String orderCode = body.getString("orderCode");
+                        String bankserial = body.getString("bankserial");
+
+                        switch (type) {
+                            case "order":
+                                rocketMQTemplate.syncSend(generalProperties.getOrderNotifyTopic(),
+                                        new OrderNotifyEvent(id, PayMethod.SANDPAY, payOrderCode, System.currentTimeMillis()));
+                                break;
+                            case "gift":
+                                giftOrderService.giftNotify(id, PayMethod.SANDPAY, payOrderCode);
+                                break;
+                            case "mintOrder":
+                                mintOrderService.mintNotify(id, PayMethod.SANDPAY, payOrderCode);
+                                break;
+                        }
+                    }
+                    return "respCode=000000";
+                } else {
+                    log.error("通知数据异常!!!");
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}

+ 2 - 2
src/main/java/com/izouma/nineth/web/StatisticController.java

@@ -73,13 +73,13 @@ public class StatisticController {
 
     @PreAuthorize("hasRole('ADMIN')")
     @GetMapping("/clearWeekTop")
-    public void clearWeekTop(){
+    public void clearWeekTop() {
         cacheService.clearWeekTop();
     }
 
     @PreAuthorize("hasRole('ADMIN')")
     @GetMapping("/clearUser")
-    public void clearUser(){
+    public void clearUser() {
         cacheService.clearUser();
     }
 }

+ 22 - 0
src/main/resources/application.yaml

@@ -220,6 +220,13 @@ adapay:
   app-public-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwN6xgd6Ad8v2hIIsQVnbt8a3JituR8o4Tc3B5WlcFR55bz4OMqrG/356Ur3cPbc2Fe8ArNd/0gZbC9q56Eb16JTkVNA/fye4SXznWxdyBPR7+guuJZHc/VW2fKH2lfZ2P3Tt0QkKZZoawYOGSMdIvO+WqK44updyax0ikK6JlNQIDAQAB
   wx-app-id:
   notify-url: https://njnft.izouma.vip/notify/adapay
+sandpay:
+  url: https://cashier.sandpay.com.cn/qr/api
+  mid: 6888806044146
+  sign-cert-path: classpath:cert/6888806044146.pfx
+  sign-cert-pwd: 123456
+  sand-cert-path: classpath:cert/sand.cer
+  notify-url: http://xiongzhu.frp.izouma.com/sandpay/notify
 rocketmq:
   name-server: 47.96.38.242:9876
   producer:
@@ -260,6 +267,18 @@ general:
   broadcast-event-topic: broadcast-event-topic-test
   register-group: register-group-test
   register-topic: register-topic-test
+wx:
+  pay:
+    notify-url: https://test.adcs.vip/notify/order/weixin
+    refund-notify-url: https://test.adcs.vip/wx/refundNotify
+    return-url: https://test.adcs.vip/9th/orders
+alipay:
+  notify-url: https://test.adcs.vip/notify/order/alipay
+  return-url: https://test.adcs.vip/9th/home
+adapay:
+  notify-url: https://test.adcs.vip/notify/adapay
+sandpay:
+  notify-url: https://test.adcs.vip/sandpay/notify
 ---
 
 spring:
@@ -352,6 +371,8 @@ alipay:
   return-url: https://www.adcs.vip/9th/home
 adapay:
   notify-url: https://www.adcs.vip/notify/adapay
+sandpay:
+  notify-url: https://www.adcs.vip/sandpay/notify
 rocketmq:
   name-server: 47.96.38.242:9876
   producer:
@@ -446,3 +467,4 @@ rocketmq:
   name-server: 172.29.50.102:9876
   producer:
     group: my-producer
+

BIN
src/main/resources/cert/6888806044146.cer


BIN
src/main/resources/cert/6888806044146.pfx


BIN
src/main/resources/cert/sand.cer


+ 61 - 2
src/test/java/com/izouma/nineth/service/AirDropServiceTest.java

@@ -1,21 +1,80 @@
 package com.izouma.nineth.service;
 
+import com.alibaba.excel.EasyExcel;
 import com.izouma.nineth.ApplicationTests;
+import com.izouma.nineth.domain.*;
+import com.izouma.nineth.dto.airDrop.AirDropDTO;
+import com.izouma.nineth.enums.AirDropType;
+import com.izouma.nineth.enums.CollectionType;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.CollectionRepo;
+import com.izouma.nineth.repo.UserRepo;
+import com.izouma.nineth.utils.excel.UploadDataListener;
+import lombok.extern.slf4j.Slf4j;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import java.io.File;
 import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+import java.util.stream.Collectors;
 
 import static org.junit.Assert.*;
 
-
+@Slf4j
 public class AirDropServiceTest extends ApplicationTests {
     @Autowired
-    AirDropService airDropService;
+    AirDropService    airDropService;
+    @Autowired
+    UserRepo          userRepo;
+    @Autowired
+    CollectionRepo    collectionRepo;
+    @Autowired
+    CollectionService collectionService;
+    @Autowired
+    AssetService      assetService;
 
     @Test
     public void drop() {
         airDropService.drop(4235490L, 4273750L, 498,
                 LocalDateTime.of(2022, 3, 25, 17, 30));
     }
+
+    @Test
+    public void drop2() throws ExecutionException, InterruptedException {
+        ForkJoinPool customThreadPool = new ForkJoinPool(1000);
+        customThreadPool.submit(() -> {
+            Long id = 273L;
+            Collection collection = collectionRepo.findById(id).orElseThrow(new BusinessException("暂无藏品"));
+            List<AirDropDTO> dtos = EasyExcel.read("/nft/xss1.xlsx")
+                    .head(AirDropDTO.class).sheet().doReadSync();
+            Set<String> phones = dtos.stream().map(AirDropDTO::getPhone).collect(Collectors.toSet());
+            List<Long> userId = userRepo.findAllByPhoneIn(phones)
+                    .stream()
+                    .map(User::getId)
+                    .collect(Collectors.toList());
+            AirDrop airDrop = new AirDrop();
+            airDrop.setUserIds(userId);
+            airDrop.setName("官方空投");
+            airDrop.setType(AirDropType.asset);
+            airDrop.setSimulateOrder(false);
+            airDrop.setIgnoreStockCheck(false);
+            airDrop.setCollectionId(collection.getId());
+            List<User> users = userRepo.findByIdInAndDelFalse(airDrop.getUserIds());
+            users.parallelStream().forEach(user -> {
+                try {
+                    assetService.createAsset(collection, user, null, null, "空投", collectionService
+                            .getNextNumber(collection.getId()));
+                    collectionService.decreaseStock(collection.getId(), 1);
+                    collectionService.increaseSale(collection.getId(), 1);
+                } catch (Exception e) {
+                    log.error("空投出错", e);
+                }
+            });
+        }).get();
+    }
 }

+ 1 - 0
src/test/java/com/izouma/nineth/service/AssetMintServiceTest.java

@@ -12,6 +12,7 @@ public class AssetMintServiceTest extends ApplicationTests {
 
     @Test
     public void mint() {
+        assetMintService.mint(141934L);
     }
 
     @Test

+ 24 - 10
src/test/java/com/izouma/nineth/service/AssetServiceTest.java

@@ -4,8 +4,12 @@ import com.alibaba.excel.EasyExcel;
 import com.alibaba.excel.annotation.ExcelProperty;
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.TokenHistory;
+import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
+import com.izouma.nineth.event.OrderNotifyEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.utils.TokenUtils;
@@ -27,6 +31,8 @@ import java.util.stream.Collectors;
 
 @Slf4j
 class AssetServiceTest extends ApplicationTests {
+    @Autowired
+    private GeneralProperties generalProperties;
     @Autowired
     private OrderRepo         orderRepo;
     @Autowired
@@ -78,7 +84,8 @@ class AssetServiceTest extends ApplicationTests {
         Collection collection = collectionRepo.findById(order.getCollectionId()).get();
         BlindBoxItem blindBoxItem = blindBoxItemRepo.findById(4250L).get();
         User user = userRepo.findById(order.getUserId()).get();
-        assetService.createAsset(blindBoxItem, user, order.getId(), order.getPrice(), "出售", 1, collection.getHoldDays());
+        assetService
+                .createAsset(blindBoxItem, user, order.getId(), order.getPrice(), "出售", 1, collection.getHoldDays());
     }
 
     @Test
@@ -137,8 +144,9 @@ class AssetServiceTest extends ApplicationTests {
         System.out.println(list.size());
         StringBuilder builder = new StringBuilder();
         list.parallelStream().forEach(tokenHistory -> {
-            List<Asset> assets = assetRepo.findByTokenIdAndCreatedAtBetween(tokenHistory.getTokenId(), tokenHistory.getCreatedAt()
-                    .minusSeconds(10), tokenHistory.getCreatedAt().plusSeconds(1));
+            List<Asset> assets = assetRepo
+                    .findByTokenIdAndCreatedAtBetween(tokenHistory.getTokenId(), tokenHistory.getCreatedAt()
+                            .minusSeconds(10), tokenHistory.getCreatedAt().plusSeconds(1));
             if (assets.size() != 1) {
                 throw new BusinessException("");
             }
@@ -203,9 +211,9 @@ class AssetServiceTest extends ApplicationTests {
             } else {
                 if (kebi.getHeibaman() > 0) {
                     for (Integer n : Arrays.stream(kebi.getNumhei()
-                                    .replaceAll("\\.$", "")
-                                    .replaceAll("^\\.", "")
-                                    .split("\\."))
+                            .replaceAll("\\.$", "")
+                            .replaceAll("^\\.", "")
+                            .split("\\."))
                             .map(String::trim)
                             .map(Integer::parseInt)
                             .collect(Collectors.toList())) {
@@ -217,9 +225,9 @@ class AssetServiceTest extends ApplicationTests {
                 }
                 if (kebi.getHuang() > 0) {
                     for (Integer n : Arrays.stream(kebi.getNumhuang()
-                                    .replaceAll("\\.$", "")
-                                    .replaceAll("^\\.", "")
-                                    .split("\\."))
+                            .replaceAll("\\.$", "")
+                            .replaceAll("^\\.", "")
+                            .split("\\."))
                             .map(String::trim)
                             .map(Integer::parseInt)
                             .collect(Collectors.toList())) {
@@ -244,7 +252,13 @@ class AssetServiceTest extends ApplicationTests {
     }
 
     @Test
-    public void cancelPublicShow(){
+    public void cancelPublicShow() {
         assetService.cancelPublic(202159L);
     }
+
+    @org.junit.Test
+    public void test3() {
+        rocketMQTemplate.syncSend(generalProperties.getOrderNotifyTopic(),
+                new OrderNotifyEvent(966653947597029376L, PayMethod.ALIPAY, "TEST20220111", System.currentTimeMillis()));
+    }
 }

+ 3 - 2
src/test/java/com/izouma/nineth/service/IdentityAuthServiceTest.java

@@ -21,8 +21,9 @@ public class IdentityAuthServiceTest extends ApplicationTests {
 
     @Test
     public void validate() {
-        identityAuthService.validate("熊竹", "15077886171", "321002199408304611");
-        identityAuthService.validate("熊竹", "15077886171", "321002199408304614");
+        identityAuthService.validateV2("王启帆", "18362933705", "32111119980516571X");
+        identityAuthService.validateV2("王启帆", "18362933705", "321111199805165712");
+        identityAuthService.validateV2("王启帆", "18362663633", "32111119980516571X");
     }
 
     @Test

+ 1 - 0
src/test/java/com/izouma/nineth/service/MintOrderServiceTest.java

@@ -5,6 +5,7 @@ import com.izouma.nineth.domain.MintMaterial;
 import com.izouma.nineth.domain.MintOrder;
 import com.izouma.nineth.domain.User;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.SandPayMethod;
 import com.izouma.nineth.repo.MintMaterialRepo;
 import com.izouma.nineth.repo.MintOrderRepo;
 import com.izouma.nineth.repo.UserRepo;

+ 4 - 0
src/test/java/com/izouma/nineth/service/OrderServiceTest.java

@@ -11,6 +11,8 @@ import com.izouma.nineth.dto.UserBankCard;
 import com.izouma.nineth.enums.AssetStatus;
 import com.izouma.nineth.enums.AuthStatus;
 import com.izouma.nineth.enums.OrderStatus;
+import com.izouma.nineth.enums.SandPayMethod;
+import com.izouma.nineth.event.OrderNotifyEvent;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.utils.FileUtils;
 import lombok.extern.slf4j.Slf4j;
@@ -257,4 +259,6 @@ public class OrderServiceTest extends ApplicationTests {
         System.out.println(orderRepo.countByUserIdAndCollectionIdAndVipTrueAndStatusIn(9850L, 196308L,
                 Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING)));
     }
+
+
 }

+ 4 - 4
src/test/java/com/izouma/nineth/service/StatisticServiceTest.java

@@ -25,10 +25,10 @@ public class StatisticServiceTest {
         System.out.println(statisticService.total());
     }
 
-    @Test
-    public void userTrend() {
-        System.out.println(statisticService.userTrend(7));
-    }
+//    @Test
+//    public void userTrend() {
+//        System.out.println(statisticService.userTrend(7));
+//    }
 
     @Test
     public void orderNumTrend() {

+ 13 - 2
src/test/java/com/izouma/nineth/service/UserServiceTest.java

@@ -39,6 +39,8 @@ public class UserServiceTest extends ApplicationTests {
     private AdapayMerchantService adapayMerchantService;
     @Autowired
     private IdentityAuthRepo      identityAuthRepo;
+    @Autowired
+    private IdentityAuthService   identityAuthService;
 
     @Test
     public void findByUsernameAndDelFalse1() {
@@ -128,7 +130,8 @@ public class UserServiceTest extends ApplicationTests {
         String bankNo = "6222024301070380165";
         String phone = "15077886171";
         User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
-        IdentityAuth identityAuth = identityAuthRepo.findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(userId, AuthStatus.SUCCESS)
+        IdentityAuth identityAuth = identityAuthRepo
+                .findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(userId, AuthStatus.SUCCESS)
                 .orElseThrow(new BusinessException("用户未认证"));
         if (identityAuth.isOrg()) {
             //throw new BusinessException("企业认证用户请绑定对公账户");
@@ -141,7 +144,9 @@ public class UserServiceTest extends ApplicationTests {
             throw new BusinessException("暂不支持此卡");
         }
 
-        adapayMerchantService.createMemberForAll(userId.toString(), user.getPhone(), identityAuth.getRealName(), identityAuth.getIdNo());
+        adapayMerchantService
+                .createMemberForAll(userId.toString(), user.getPhone(), identityAuth.getRealName(), identityAuth
+                        .getIdNo());
         user.setMemberId(user.getId().toString());
         userRepo.save(user);
 
@@ -187,4 +192,10 @@ public class UserServiceTest extends ApplicationTests {
             userService.removeBankCard(aLong);
         }
     }
+
+    @Test
+    public void testIdentity() {
+
+        identityAuthService.auth(identityAuthRepo.findById(590L).orElseThrow(new BusinessException("2")));
+    }
 }