官方提供的签名和验签方法,替换上自己的支付系统秘钥 SALT
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;
public class DouYinSign {
/**
* 担保支付请求不参与签名参数
* app_id 小程序appID
* thirdparty_id 代小程序进行该笔交易调用的第三方平台服务商id
* sign 签名
* other_settle_params 其他分账方参数
*
* Guaranteed payment requests do not participate in signature parameters
* app_id Applets appID
* thirdparty_id The id of the third-party platform service provider that calls the transaction on behalf of the Applets
* sign sign
* other_settle_params Other settle params
*/
public final static List REQUEST_NOT_NEED_SIGN_PARAMS = Arrays.asList("app_id", "thirdparty_id", "sign", "other_settle_params");
/**
* 支付密钥值,需要替换为自己的密钥(完成进件后,开发者可在字节开放平台-【某小程序】-【功能】-【支付】-【担保交易设置】中查看支付系统秘钥 SALT)
*
* Payment key value, you need to replace it with your own key
*/
private static final String SALT = "支付系统秘钥 SALT";
/**
* RequestSign 担保支付请求签名算法
* @param paramsMap {@code Map} 所有的请求参数
* @return:签名字符串
*
* RequestSign Guaranteed payment request signature algorithm
* @param paramsMap {@code Map} all request parameters
* @return: Signature string
*/
public static String requestSign(Map paramsMap) {
List paramsArr = new ArrayList<>();
for (Map.Entry entry : paramsMap.entrySet()) {
String key = entry.getKey();
if (REQUEST_NOT_NEED_SIGN_PARAMS.contains(key)) {
continue;
}
String value = entry.getValue().toString();
value = value.trim();
if (value.startsWith("\"") && value.endsWith("\"") && value.length() > 1) {
value = value.substring(1, value.length() - 1);
}
value = value.trim();
if (value.equals("") || value.equals("null")) {
continue;
}
paramsArr.add(value);
}
paramsArr.add(SALT);
Collections.sort(paramsArr);
StringBuilder signStr = new StringBuilder();
String sep = "";
for (String s : paramsArr) {
signStr.append(sep).append(s);
sep = "&";
}
return md5FromStr(signStr.toString());
}
/**
* CallbackSign 担保支付回调签名算法
* @param params {@code List} 所有字段(验证时注意不包含 sign 签名本身,不包含空字段与 type 常量字段)内容与平台上配置的 token
* @return:签名字符串
*
* CallbackSign Guaranteed payment callback signature algorithm
* @param params {@code List} The content of all fields (note that the sign signature itself is not included during verification, and does not include empty fields and type constant fields) content and the token configured on the platform
* @return: signature string
*/
public static String callbackSign(List params) {
try {
String concat = params.stream().sorted().collect(Collectors.joining(""));
byte[] arrayByte = concat.getBytes(StandardCharsets.UTF_8);
MessageDigest mDigest = MessageDigest.getInstance("SHA1");
byte[] digestByte = mDigest.digest(arrayByte);
StringBuffer signBuilder = new StringBuffer();
for (byte b : digestByte) {
signBuilder.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return signBuilder.toString();
} catch (Exception exp) {
return "";
}
}
/**
* md5FromStr md5算法对该字符串计算摘要
* @param inStr {@code String} 需要签名的字符串
* @return:签名字符串
*
* md5FromStr The md5 algorithm computes a digest of the string
* @param inStr {@code String} String to be signed
* @return: signature string
*/
private static String md5FromStr(String inStr) {
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
byte[] byteArray = inStr.getBytes(StandardCharsets.UTF_8);
byte[] md5Bytes = md5.digest(byteArray);
StringBuilder hexValue = new StringBuilder();
for (byte md5Byte : md5Bytes) {
int val = ((int) md5Byte) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
}
public String preOrder(String appId, String notify_url, Integer total_amount, String orderNo, String subject, String detail){
// 示例参数
Map params = new HashMap<>();
params.put("app_id",app_id);
params.put("out_order_no", orderNo); //商户订单号
params.put("total_amount", total_amount); //金额。分
params.put("notify_url", notify_url); //回调接口
params.put("subject", subject); //主题
params.put("body", detail); //商品详情
params.put("valid_time", 5*60); //过期时间
String sign = DouYinSign.requestSign(params); //签名
params.put("sign",sign);
//发起post请求
String postJson = doPostJson("https://developer.toutiao.com/api/apps/ecpay/v1/create_order", JSONObject.toJSONString(params), "UTF-8");
if (StringUtils.isNoneBlank(postJson)) {
JSONObject jsonObject = JSONObject.parseObject(postJson);
if ("success".equals(jsonObject.get("err_tips")))
{
return jsonObject.get("data").toString();
} else {
throw new GlobalException("抖音支付生成失败" + postJson);
}
}
return null;
}
public String dyNotify(HttpServletRequest request, HttpServletResponse response) {
try {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
InputStream in = request.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
in.close();
String responseJson = new String(out.toByteArray(),"utf-8");
logger.info("responseJson:{}",responseJson);
JSONObject jsonObject = JSONObject.parseObject(responseJson);
JSONObject msgJson = jsonObject.getJSONObject("msg");
String resultCode = jsonObject.getString("type");
if ("payment".equalsIgnoreCase(resultCode)) {
List sortedString = new ArrayList<>();
//token
sortedString.add("token");
//时间戳
sortedString.add(jsonObject.getString("timestamp"));
//随机数
sortedString.add(jsonObject.getString("nonce"));
//msg
sortedString.add(jsonObject.getString("msg"));
Collections.sort(sortedString);
StringBuilder sb = new StringBuilder();
sortedString.forEach(sb::append);
String msg_signature = jsonObject.getString("msg_signature");
String sign = DouYinSign.callbackSign(sortedString);
logger.info("支付回调接口密钥签名:{}",sign);
if(!sign.equals(msg_signature)) {//判断签名,此处验签有问题
JSONObject resultJson = new JSONObject();
resultJson.put("err_no",8);
resultJson.put("err_tips","error");
}
String orderNo = msgJson.getString("cp_orderno"); //商户订单号
String order_id = msgJson.getString("order_id"); //抖音订单id
Integer orderAmount = msgJson.getInteger("total_amount"); //支付金额
//todo 处理支付成功后的订单业务
JSONObject resultJson = new JSONObject();
resultJson.put("err_no",0);
resultJson.put("err_tips","success");
//todo 同步订单变成已支付
return resultJson.toString();
} else {
//订单编号
String outTradeNo = msgJson.getString("cp_orderno");
logger.error("订单" + outTradeNo + "支付失败");
JSONObject resultJson = new JSONObject();
resultJson.put("err_no",0);
resultJson.put("err_tips","error");
return resultJson.toString();
}
} catch (Exception e) {
e.printStackTrace();
JSONObject resultJson = new JSONObject();
resultJson.put("err_no",8);
resultJson.put("err_tips","error");
return resultJson.toString();
}
}
后续还需要同步订单,担保支付的订单需要同步到抖音商场订单里面
验签参考文章:https://blog.csdn.net/m0_59963277/article/details/123048704