sortedString = new ArrayList<>();
+ sortedString.add(String.valueOf(timestamp));
+ sortedString.add(nonce);
+ sortedString.add(msg);
+ sortedString.add(token);
+ Collections.sort(sortedString);
+ StringBuilder sb = new StringBuilder();
+ sortedString.forEach(sb::append);
+ return getSha1(sb.toString().getBytes());
+ }
+
+ public String getSha1(byte[] input) {
+ MessageDigest mDigest;
+ try {
+ mDigest = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return "";
+ }
+ byte[] result = mDigest.digest(input);
+ StringBuilder sb = new StringBuilder();
+ for (byte b : result) {
+ sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 手机号登陆信息解密
+ */
+ public String decrypt(String encryptedData, String sessionKey, String iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
+ Base64.Decoder decoder = Base64.getDecoder();
+ byte[] sessionKeyBytes = decoder.decode(sessionKey);
+ byte[] ivBytes = decoder.decode(iv);
+ byte[] encryptedBytes = decoder.decode(encryptedData);
+
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ SecretKeySpec skeySpec = new SecretKeySpec(sessionKeyBytes, "AES");
+ IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
+ cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
+ byte[] ret = cipher.doFinal(encryptedBytes);
+ return new String(ret);
+ }
+}
+
diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/KSPayUtil.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/KSPayUtil.java
new file mode 100644
index 0000000..4d694d2
--- /dev/null
+++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/KSPayUtil.java
@@ -0,0 +1,133 @@
+package com.bnyer.pay.utils;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static javax.crypto.Cipher.DECRYPT_MODE;
+
+/**
+ * @author :WXC
+ * @Date :2023/04/23
+ * @description :快手支付工具类
+ */
+@Slf4j
+@Component
+public class KSPayUtil {
+
+ /**
+ * 快手小程序返回的加密数据的解密函数
+ *
+ * @param sessionKey 有效的sessionKey,通过 login code 置换
+ * @param encryptedData 返回的加密数据(base64编码)
+ * @param iv 返回的加密IV(base64编码)
+ * @return 返回解密的字符串数据
+ */
+ public String decrypt(String sessionKey, String encryptedData, String iv) {
+ // Base64解码数据
+ byte[] aesKey = Base64.decodeBase64(sessionKey);
+ byte[] ivBytes = Base64.decodeBase64(iv);
+ byte[] cipherBytes = Base64.decodeBase64(encryptedData);
+
+ byte[] plainBytes = decrypt0(aesKey, ivBytes, cipherBytes);
+
+ return new String(plainBytes, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * AES解密函数. 使用 AES/CBC/PKCS5Padding 模式
+ *
+ * @param aesKey 密钥,长度16
+ * @param iv 偏移量,长度16
+ * @param cipherBytes 密文信息
+ * @return 明文
+ */
+ private byte[] decrypt0(byte[] aesKey, byte[] iv, byte[] cipherBytes) {
+ SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
+ IvParameterSpec ivSpec = new IvParameterSpec(iv);
+ try {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(DECRYPT_MODE, keySpec, ivSpec);
+ return cipher.doFinal(cipherBytes);
+ } catch (Exception e) {
+ log.error("decrypt error:{}", e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * https://mp.kuaishou.com/docs/develop/server/epay/interfaceDefinition.html
+ *
+ * https://mp.kuaishou.com/docs/develop/server/payment/serverSignature.html
+ * 支付签名
+ */
+
+ public String buildMd5(Map dataMap, String appSecret) {
+ String signStr = genSignStr(dataMap);
+ return DigestUtils.md5Hex(signStr + appSecret);
+ }
+
+ private String genSignStr(Map data) {
+ StringBuilder sb = new StringBuilder();
+ data.keySet().stream().sorted()
+ .filter(key -> StringUtils.isNotBlank(key) && StringUtils.isNotEmpty(data.get(key)))
+ .forEach(key -> {
+ sb.append(key);
+ sb.append("=");
+ sb.append(data.get(key));
+ sb.append("&");
+ });
+ if (sb.length() > 0) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+
+ return sb.toString();
+ }
+
+
+ /**
+ * 获取参数 Map 的签名结果
+ * https://mp.kuaishou.com/docs/develop/server/epay/appendix.html
+ *
+ * @param signParamsMap 含义见上述示例
+ * @return 返回签名结果
+ */
+ public String calcSign(Map signParamsMap, String secret) {
+ // 去掉 value 为空的
+ Map trimmedParamMap = signParamsMap.entrySet()
+ .stream()
+ .filter(item -> !Strings.isNullOrEmpty(item.getValue().toString()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+ // 按照字母排序
+ Map sortedParamMap = trimmedParamMap.entrySet()
+ .stream()
+ .sorted(Map.Entry.comparingByKey())
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ (oldValue, newValue) -> oldValue, LinkedHashMap::new));
+
+ // 组装成待签名字符串。(注,引用了guava工具)
+ String paramStr = Joiner.on("&").withKeyValueSeparator("=").join(sortedParamMap.entrySet());
+ String signStr = paramStr + secret;
+
+ // 生成签名返回。(注,引用了commons-codec工具)
+ return DigestUtils.md5Hex(signStr);
+ }
+}
+
diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java
new file mode 100644
index 0000000..2022f05
--- /dev/null
+++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java
@@ -0,0 +1,107 @@
+package com.bnyer.pay.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.bnyer.common.core.domain.KspayConfig;
+import com.bnyer.pay.constant.KSPayConstants;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author :WXC
+ * @Date :2023/04/24
+ * @description :
+ */
+@Slf4j
+@Component
+public class PayRestTemplateUtil {
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ /**
+ * 快手小程序post请求
+ * code2session
+ */
+ public String ksPostRequestUrlencoded(JSONObject jsonObject, String url) {
+ String result = "";
+ try {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("js_code={0}&app_id={1}&&app_secret={2}", jsonObject.get("js_code"), jsonObject.get("appid"), jsonObject.get("secret")), headers);
+ result = restTemplate.postForObject(url, formEntity, String.class);
+ } catch (Exception e) {
+ log.error("快手小程序post请求异常{}", url);
+ log.error("post请求异常:{}", e.getMessage());
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ /**
+ * 快手小程序获取accessToken
+ */
+ public String ksPostRequestUrlencoded(KspayConfig kspayConfig) {
+ String result = "";
+ try {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("app_id={0}&app_secret={1}&&grant_type={2}"
+ , kspayConfig.getAppid(), kspayConfig.getSecret(), "client_credentials"), headers);
+ result = restTemplate.postForObject(KSPayConstants.GET_ACCESS_TOKEN, formEntity, String.class);
+ } catch (Exception e) {
+ log.error("快手小程序post请求异常{}", KSPayConstants.GET_ACCESS_TOKEN);
+ log.error("post请求异常:{}", e.getMessage());
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ /**
+ * 快手
+ * 支付 退款 结算
+ */
+ public String ksPostRequestJson(JSONObject jsonObject, String url, String appId, String accessToken) {
+ String result = "";
+ try {
+ Map map = new HashMap<>();
+ map.put("app_id", appId);
+ map.put("access_token", accessToken);
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ HttpEntity