1.开度小程序后天开发配置
1.1 使用rsa 密钥生成工具,生成公钥私钥,使用工具,推荐阿里的生成工具https://help.aliyun.com/document_detail/123151.html。
1.2.支付设置后台配置,公钥和回调调地址。
2.生成预支付订单
/**
* 拼装参数
* @param transactionSN
* @param tradeAmount
* @return
*/
private BaiduPayApplyRespDto getBaiduPayApplyRespDto(String transactionSN, String tradeAmount) {
BaiduPayApplyRespDto result = new BaiduPayApplyRespDto();
OrderInfo order = new OrderInfo();
Map<String,Object> sign = new HashMap<>();
Map<String,Object> bizInfo = new HashMap<>();
order.setAppKey(appKey);
order.setDealId(dealId);
order.setDealTitle("支付订单");
order.setTotalAmount(tradeAmount);
order.setTpOrderId(transactionSN);
order.setSignFieldsRange("1");
//组装参数
sign.put("appKey",appKey);
sign.put("dealId",dealId);
sign.put("totalAmount", tradeAmount);
sign.put("tpOrderId", transactionSN);
order.setBizInfo(bizInfo);
try {
log.info("【参数签名】:{}", JSONObject.toJSONString(sign));
order.setRsaSign(RSASign.sign(sign,privateKey));
} catch (Exception e) {
log.error("【参数签名失败】:", e);
return null;
}
result.setOrderInfo(order);
return result;
}
2.2 签名部分代码
/*
* Copyright (C) 2020 Baidu, Inc. All Rights Reserved.
*/
package com.baidu.*;
import static org.springframework.util.Assert.isTrue;
import static org.springframework.util.Assert.notNull;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* 百度交易中台双向RSA签名工具
* JDK版本要求:1.8+
*/
public class RSASign {
private static final String CHARSET = "UTF-8";
private static final String SIGN_TYPE_RSA = "RSA";
private static final String SIGN_ALGORITHMS = "SHA1WithRSA";
private static final String SIGN_KEY = "rsaSign";
/**
* 使用私钥生成签名字符串
*
* @param params 待签名参数集合
* @param privateKey 私钥原始字符串
*
* @return 签名结果字符串
*
* @throws Exception
*/
public static String sign(Map<String, Object> params, String privateKey) throws Exception {
isTrue(!CollectionUtils.isEmpty(params), "params is required");
notNull(privateKey, "privateKey is required");
String signContent = signContent(params);
Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
signature.initSign(getPrivateKeyPKCS8(privateKey));
signature.update(signContent.getBytes(CHARSET));
byte[] signed = signature.sign();
return new String(Base64.getEncoder().encode(signed));
}
/**
* 使用公钥校验签名
*
* @param params 入参数据,签名属性名固定为rsaSign
* @param publicKey 公钥原始字符串
*
* @return true 验签通过 | false 验签不通过
*
* @throws Exception
*/
public static boolean checkSign(Map<String, Object> params, String publicKey) throws Exception {
isTrue(!CollectionUtils.isEmpty(params), "params is required");
notNull(publicKey, "publicKey is required");
// sign & content
String content = signContent(params);
String rsaSign = params.get(SIGN_KEY).toString();
// verify
Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
signature.initVerify(getPublicKeyX509(publicKey));
signature.update(content.getBytes(CHARSET));
return signature.verify(Base64.getDecoder().decode(rsaSign.getBytes(CHARSET)));
}
/**
* 对输入参数进行key过滤排序和字符串拼接
*
* @param params 待签名参数集合
*
* @return 待签名内容
*
* @throws UnsupportedEncodingException
*/
private static String signContent(Map<String, Object> params) throws UnsupportedEncodingException {
Map<String, String> sortedParams = new TreeMap<>(Comparator.naturalOrder());
for (Map.Entry<String, Object> entry : params.entrySet()) {
String key = entry.getKey();
if (legalKey(key)) {
sortedParams.put(key, entry.getValue().toString());
}
}
StringBuilder builder = new StringBuilder();
if (!CollectionUtils.isEmpty(sortedParams)) {
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
builder.append(entry.getKey());
builder.append("=");
builder.append(entry.getValue());
builder.append("&");
}
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
}
/**
* 将公钥字符串进行Base64 decode之后,生成X509标准公钥
*
* @param publicKey 公钥原始字符串
*
* @return X509标准公钥
*
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
*/
private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException,
NoSuchAlgorithmException, UnsupportedEncodingException {
if (StringUtils.isEmpty(publicKey)) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
byte[] decodedKey = Base64.getDecoder().decode(publicKey.getBytes(CHARSET));
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
}
/**
* 将私钥字符串进行Base64 decode之后,生成PKCS #8标准的私钥
*
* @param privateKey 私钥原始字符串
*
* @return PKCS #8标准的私钥
*
* @throws Exception
*/
private static PrivateKey getPrivateKeyPKCS8(String privateKey) throws Exception {
if (StringUtils.isEmpty(privateKey)) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
byte[] decodedKey = Base64.getDecoder().decode(privateKey.getBytes(CHARSET));
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
}
/**
* 有效的待签名参数key值
* 非空、且非签名字段
*
* @param key 待签名参数key值
*
* @return true | false
*/
private static boolean legalKey(String key) {
return StringUtils.hasText(key) && !SIGN_KEY.equalsIgnoreCase(key);
}
}
3.小程序前端调起支付。
4.回调处理
4.1,回调部分java代码实现 ,这里唯一需要注意的就是验签的公钥,这里是百度小程序后台,支付设置里面的平台公钥,不是开发者公钥
@Data
public class BaiduPayReq implements Serializable {
/**
* 交易单号
*/
private String tpOrderId;
/**
* 订单状态
*/
private Integer status;
/**
* 百度交易号
*/
private String orderId;
/**
* 订单实际支付金额
*/
private Integer payMoney;
/**
* 支付时间
*/
private Integer payTime;
/**
* 消息id
*/
private String messageId;
/**
* 签名
*/
private String rsaSign;
/**
*
*/
private String dealId;
/**
*
*/
private Long userId;
private Integer unitPrice;
private Integer count;
private Integer totalMoney;
private Integer promoMoney;
private Integer hbMoney;
private Integer hbBalanceMoney;
private Integer giftCardMoney;
private Object promoDetail;
private Integer payType;
private Object returnData;
private Integer partnerId;
}
/**
* 百度支付回调地址
*
* @return
*/
@RequestMapping(value = "/callback")
public JSONObject notifyPay(BaiduPayReq req) {
log.info("BaiduPayReq:{}", JSONObject.toJSONString(req));
JSONObject resultObj = new JSONObject();
//是百度小程序后台支付设置里面的平台公钥,不是开发者公钥,切记
String baiduPayPublicKey="";
try {
//支付参数验签
Map<String, Object> sign = JSON.parseObject(JSON.toJSONString(req), new TypeReference<Map<String, Object>>() {
});
sign.put("rsaSign", req.getRsaSign().replace(" ", ""));
log.info("【百度支付验签入参】:{}", JSONObject.toJSONString(sign));
boolean check = RSASign.checkSign(sign, baiduPayPublicKey);
log.info("【百度支付验签结果】:{}", check);
if (!check) {
return resultObj;
}
//处理业务逻辑
}catch (Exception e){
log.error("【baiduPay notifyPay】 error:{}", e);
}
return resultObj;
}