一.說明和前期準(zhǔn)備(小程序的V3版本)
特別說明:遇到 java.security.InvalidKeyException: Illegal key size ******* getValidator的錯誤
參考添加鏈接描述
JDK7的下載地址
JDK8的下載地址:
下載后解壓,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
如果安裝了JRE,將兩個jar文件放到%JRE_HOME%\lib\security目錄下覆蓋原來的文件
如果安裝了JDK,還要將兩個jar文件也放到%JDK_HOME%\jre\lib\security目錄下覆蓋原來文件。
1.場景:小程序項目需要對接微信小程序的支付接口,這里使用的是V3版本
官方文檔鏈接:微信小程支付申請文檔鏈接
1.1按照上面的文檔申請支付
拿到相關(guān)的參數(shù)(小程序的Appid,小程序的appSecret ,商戶秘鑰 PrivateKey,商戶號 Mchid,證書序列號MchSerialNo,V3秘鑰))本人讀取方式為從配置文件中讀取
package com.wxapplet.model;
import java.io.IOException;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Value;
import com.foc.alipay.config.AlipayServiceEnvConstants;
import com.foc.common.util.Constants;
public class WxV3payConfig {
static {
/**
* 靜態(tài)代碼塊初始化類變量
*/
Properties proper = new Properties();
try {
proper.clear();
proper.load(WxV3payConfig.class.getResourceAsStream("/paykey.properties"));
appletAppid=proper.getProperty("appletAppid");
appletMchid=proper.getProperty("appletMchid");
appletPrivateKeyPath=proper.getProperty("appletPrivateKeyPath");
appletMchSerialNo=proper.getProperty("appletMchSerialNo");
appletSecret=proper.getProperty("appletSecret");
appletApiV3Key=proper.getProperty("appletApiV3Key");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String appletAppid;// 由微信生成的應(yīng)用ID
public static String appletMchid;// 直連商戶的商戶號,由微信支付生成并下發(fā)。
public static String appletPrivateKeyPath;// 小程序商戶秘鑰
public static String appletMchSerialNo;// 商戶證書序列號
public static String appletSecret;// 小程序 appSecret
public static String appletApiV3Key;// V3密鑰
@Value("${appletAppid}")
public static void setAppletAppid(String appletAppid) {
WxV3payConfig.appletAppid = appletAppid;
}
@Value("${appletMchid}")
public static void setAppletMchid(String appletMchid) {
WxV3payConfig.appletMchid = appletMchid;
}
@Value("${appletPrivateKeyPath}")
public static void setAppletPrivateKey(String appletPrivateKeyPath) {
WxV3payConfig.appletPrivateKeyPath = appletPrivateKeyPath;
}
@Value("${appletMchSerialNo}")
public static void setAppletMchSerialNo(String appletMchSerialNo) {
WxV3payConfig.appletMchSerialNo = appletMchSerialNo;
}
@Value("${appletSecret}")
public static void setAppletSecret(String appletSecret) {
WxV3payConfig.appletSecret = appletSecret;
}
@Value("${appletApiV3Key}")
public static void setAppletApiV3Key(String appletApiV3Key) {
WxV3payConfig.appletApiV3Key = appletApiV3Key;
}
}
對應(yīng)的paykey.properties文件
#v3
#ying yong id
appletAppid=
#shang hu hao
appletMchid=
appletPrivateKeyPath=appletPrivateKey.pem
#zheng shu xu lie hao
appletMchSerialNo=
#appsectet
appletSecret=
#v3 mi yao
appletApiV3Key=
2.引入相關(guān)的maven包
2.1參考的微信官方文檔
微信開發(fā)指引
微信java給出的示例
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
3.相關(guān)工具類
3.1發(fā)送http請求工具類
package com.wxapplet.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
@Component
public class AppletHttpClient {
/**
* 將通知參數(shù)轉(zhuǎn)化為字符串
*
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null;) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 獲取商戶的私鑰文件
*
* @param filename
* @return
*/
public PrivateKey getPrivateKey(String filename) {
InputStream insss = AppletHttpClient.class.getClassLoader().getResourceAsStream(filename);
// InputStream insss = ClassLoader.getSystemResourceAsStream(filename);
return PemUtil.loadPrivateKey(insss);
}
/**
* 獲取http請求對象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(AutoUpdateCertificatesVerifier verifier) {
// 獲取商戶私鑰
PrivateKey privateKey = getPrivateKey(WxV3payConfig.appletPrivateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(WxV3payConfig.appletMchid, WxV3payConfig.appletMchSerialNo, privateKey).withValidator(new WechatPay2Validator(verifier));
// ... 接下來,你仍然可以通過builder設(shè)置各種參數(shù),來配置你的HttpClient
// 通過WechatPayHttpClientBuilder構(gòu)造的HttpClient,會自動的處理簽名和驗簽,并進行證書自動更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 獲取HttpClient,無需進行應(yīng)答簽名驗證,跳過驗簽的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
// 獲取商戶私鑰
PrivateKey privateKey = getPrivateKey(WxV3payConfig.appletPrivateKeyPath);
// 用于構(gòu)造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
// 設(shè)置商戶信息
.withMerchant(WxV3payConfig.appletMchid, WxV3payConfig.appletMchSerialNo, privateKey)
// 無需進行簽名驗證、通過withValidator((response) -> true)實現(xiàn)
.withValidator((response) -> true);
// 通過WechatPayHttpClientBuilder構(gòu)造的HttpClient,會自動的處理簽名和驗簽,并進行證書自動更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* V3 SHA256withRSA 簽名.
* @param appid
* @param timeStamp
* @param nonceStr
* @param prepayId
* @param privateKey
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
* @throws FileNotFoundException
*/
public String getSign1(String appid ,String timeStamp,String nonceStr ,String prepayId , String privateKeyPath) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, FileNotFoundException{
try {
String signatureStr = Stream.of(appid, timeStamp, nonceStr, prepayId)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
InputStream insss = PayCommonUtil.class.getClassLoader().getResourceAsStream(privateKeyPath);
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(insss);
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 獲取簽名驗證器
*
* @return
*/
@Bean
public AutoUpdateCertificatesVerifier getVerifier() {
// 獲取商戶私鑰
PrivateKey privateKey = getPrivateKey(WxV3payConfig.appletPrivateKeyPath);
// 私鑰簽名對象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(WxV3payConfig.appletMchSerialNo, privateKey);
// 身份認證對象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(WxV3payConfig.appletMchid, privateKeySigner);
// 使用定時更新的簽名驗證器,不需要傳入證書
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(wechatPay2Credentials,
WxV3payConfig.appletApiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
}
3.2進行下單操作
這里使用JSAPI下單,微信小程序JSAPI下單參考
String appId =WxV3payConfig.appletAppid; //
String mchId =WxV3payConfig.appletMchid; // 商戶號
String privateKeyPath =WxV3payConfig.appletPrivateKeyPath;// 你的商戶私鑰路徑
String nonceStr = RandomUtil.randomString(32);//隨機字符串
// 構(gòu)造訂單信息
JSONObject jsonObject = new JSONObject();
jsonObject.put("appid",appId); // 小程序的應(yīng)用appid
jsonObject.put("mchid", mchId); // 直連商戶的商戶號,由微信支付生成并下發(fā)。
String body = "訂單消費";
Project project = projectService.findOne(tempPersonalRecord.getProjectId());
if (project != null && StringUtils.isNotBlank(project.getName())) {
body = project.getName();
}
jsonObject.put("description", body); // 商品描述
jsonObject.put("out_trade_no", outTradeNo); // 商戶訂單號
logger.info("total_fee=" + Math.round(tempPersonalRecord.getMoney() * 100) + "");
JSONObject amoutJson = new JSONObject();
amoutJson.put("total", tempPersonalRecord.getMoney() * 100); // 總金額,訂單總金額,單位為分
amoutJson.put("currency", "CNY");// 貨幣類型CNY:人民幣,境內(nèi)商戶號僅支持人民幣。
jsonObject.put("amount", amoutJson); // 訂單金額信息
jsonObject.put("notify_url", notifyUrl); // 接收微信支付異步通知回調(diào)地址,通知url必須為直接可訪問的url,不能攜帶參數(shù)。
JSONObject payUser = new JSONObject();
payUser.put("openid", mfUser.getOpenId());
jsonObject.put("payer", payUser); // 支付者用戶在商戶appid下的唯一標(biāo)識。
// 發(fā)送請求進行JSAPI下單
HttpPost httpPost = new HttpPost(APPLET_JSAPI_URL);
StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成簽名并執(zhí)行請求
CloseableHttpResponse response = wxPayClient.execute(httpPost);
String reString = EntityUtils.toString(response.getEntity());//響應(yīng)體
int statusCode = response.getStatusLine().getStatusCode();//響應(yīng)狀態(tài)碼
if (statusCode==200) {//處理成功
logger.info("微信小程序支付響應(yīng)成功,返回的結(jié)果為="+reString);
}else if (statusCode==204) {//處理成功無返回的boy
logger.info("微信小程序支付響應(yīng)成功");
}else {
logger.info("微信小程序響應(yīng)失敗,響應(yīng)碼為="+statusCode+",返回的結(jié)果為="+reString);
return new ResultVO(ResultCode.ORDERERROR);
}
SortedMap<String, String> params = new TreeMap<String, String>();
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);// 時間戳
String preId = "prepay_id=" + JSON.parseObject(reString).getString("prepay_id");
String paySign = appletHttpClient.getSign1(appId, timeStamp, nonceStr, preId,
privateKeyPath);// 簽名
rr.put("appId", appId);// appid
rr.put("timeStamp", timeStamp);// 時間戳
rr.put("nonceStr", nonceStr);// 隨機字符串
rr.put("package", "prepay_id=" + JSON.parseObject(reString).getString("prepay_id"));// 訂單詳情擴展字符串
rr.put("signType", "RSA");// 簽名類型,默認為RSA,僅支持RSA
rr.put("paySign", paySign);// 簽名
rr.put("outTradeNo", tempPersonalRecord.getOutTradeNo());//訂單編號
return new ResultVO(ResultCode.SUCCESS, rr);
3.3獲取返回的prepay_id稍后返回給前端
4.向前端傳遞調(diào)用wx.requestPayment(OBJECT)發(fā)起微信支付的參數(shù)
Api地址
特別說明:簽名使用的是SHA256 with RSA簽名,并對簽名結(jié)果進行Base64編碼得到簽名值。
4.1生成簽名的工具類
/**
* V3 SHA256withRSA 簽名.
* @param appid
* @param timeStamp
* @param nonceStr
* @param prepayId
* @param privateKey
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
*/
public static String getSign(String appid ,String timeStamp,String nonceStr ,String prepayId , String privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException{
try {
String signatureStr = Stream.of(appid, timeStamp, nonceStr, prepayId)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
官方簽名參考地址
4.2簽名的生成
String nonceStr = RandomUtil.randomString(32);// 隨機字符串
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);// 時間戳
String preId = "prepay_id=" + JSON.parseObject(reString).getString("prepay_id");
String paySign = PayCommonUtil.getSign(weChatPub.getAppletAppid(), timeStamp, nonceStr, preId,
weChatPub.getAppletPrivateKey());// 簽名
rr.put("appId", weChatPub.getAppletAppid());// appid
rr.put("timeStamp", timeStamp);// 時間戳
rr.put("nonceStr", nonceStr);// 隨機字符串
rr.put("package", "prepay_id=" + JSON.parseObject(reString).getString("prepay_id"));// 訂單詳情擴展字符串
rr.put("signType", "RSA");// 簽名類型,默認為RSA,僅支持RSA
rr.put("paySign", paySign);// 簽名
return new ResultVO(ResultCode.SUCCESS, rr);
4.3使用對生成的簽名使用官方工具驗簽
工具下載地址
5.支付成功后微信的回調(diào)
官方的驗簽和解密
// 構(gòu)建request,傳入必要參數(shù)
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
.withNonce(nonce)
.withTimestamp(timestamp)
.withSignature(signature)
.withBody(body)
.build();
NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
// 驗簽和解析請求體
Notification notification = handler.parse(request);
// 從notification中獲取解密報文
System.out.println(notification.getDecryptData());
相關(guān)示例代碼(包含驗簽操作)自己實現(xiàn)的代碼文章來源:http://www.zghlxwxcb.cn/news/detail-483327.html
logger.info("支付成功進入回調(diào)方法--" + DateUtil.formatDate(new Date()));
Map<String, String> map1 = new HashMap<>();// 應(yīng)答對象
Gson gson = new Gson();
Map<String, String> map = new HashMap<>();// 應(yīng)答對象
String outTradeNo = null;// 商戶訂單號
String transactionId = null;// 微信支付訂單號
String timeEnd = null;//
String openid = null;// 用戶標(biāo)識
String tradeState = null;// 交易狀態(tài)
// 處理通知參數(shù)
String body = appletHttpClient.readData(request);
Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String) bodyMap.get("id");
// 獲取驗簽器
// 加載平臺證書(mchId:商戶號,mchSerialNo:商戶證書序列號,apiV3Key:V3密鑰)
PrivateKey privateKey =appletHttpClient.getPrivateKey(WxV3payConfig.appletPrivateKeyPath);
AutoUpdateCertificatesVerifier verifier = appletHttpClient.getVerifier();
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(
verifier, requestId, body);
// 進行驗簽操作
if (!wechatPay2ValidatorForRequest.validate(request)) {// 驗簽成功
logger.error("支付通知驗簽失敗");
// 失敗應(yīng)答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "通知驗簽失敗");
return gson.toJson(map);
}
logger.info("支付通知驗證成功");
// 獲取明文
String plainText = wxAPIV3AesUtil.decryptFromResource(bodyMap, WxV3payConfig.appletApiV3Key);
// 將明文轉(zhuǎn)換成map
Map<String, String> resultMap = JSON.parseObject(plainText, HashMap.class);
outTradeNo = resultMap.get("out_trade_no");// 商戶訂單號
transactionId = resultMap.get("transaction_id");// 微信支付訂單號
timeEnd = resultMap.get("time_end");//
openid = resultMap.get("openid");// 用戶標(biāo)識
tradeState = resultMap.get("trade_state");// 交易狀態(tài)
5.1驗簽的工具類
package com.wxapplet.util;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
public class WechatPay2ValidatorForRequest {
protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
/**
* 應(yīng)答超時時間,單位為分鐘
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String requestId;
protected final String body;
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
//·驗簽
public final boolean validate(HttpServletRequest request) throws IOException {
try {
//處理請求參數(shù)
validateParameters(request);
//構(gòu)造驗簽名串
String message = buildMessage(request);
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//驗簽
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
protected final void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判斷請求是否過期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒絕過期請求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
protected final String buildMessage(HttpServletRequest request) throws IOException {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
}
5.2 解密回調(diào)通知的數(shù)據(jù)相關(guān)工具類
@Component
public class WxAPIV3AesUtil {
/**
* 對稱解密
*
* @param bodyMap
* @return
*/
public static String decryptFromResource(Map<String, Object> bodyMap, String apiV3Key)
throws GeneralSecurityException {
// 通知數(shù)據(jù)
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
// 數(shù)據(jù)密文
String ciphertext = resourceMap.get("ciphertext");
// 隨機串
String nonce = resourceMap.get("nonce");
// 附加數(shù)據(jù)
String associatedData = resourceMap.get("associated_data");
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
return plainText;
}
}
參考鏈接
官方微信V3排錯指南
參考1
參考2文章來源地址http://www.zghlxwxcb.cn/news/detail-483327.html
到了這里,關(guān)于微信小程序支付V3版本接口實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!