一 和旧版的差异
1 数据交互格式由原先的xml改为json
2 接口请求方式由原来的无需额外请求头变为需要Authorization请求头
3 几乎所有接口都需要使用证书计算签名或者Token
4 支付回调使用AES-256-GCM进行加密,安全性提高
5 扫码支付无需再设置扫码回调url
6 无需像之前一样调用长链接转短链接接口提高扫码支付识别成功率
总体来说变化还是挺大的,开发时只需要正确计算token和签名就没什么问题了
开发前必读:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
二 分步拆解技术难点
1 token的计算,这一步算是最简单的只需按文档写工具代码即可
public class WxPayTokenUtilV3 {
/**
* 生成Token.
*
* @param mchId 商户号
* @param nonceStr 随机字符串
* @param timestamp 时间戳
* @param serialNo 证书序列号
* @param signature 签名
* @return the string
*/
public static String token(String mchId, String nonceStr, long timestamp, String serialNo, String signature) {
String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
// 生成token
return String.format(TOKEN_PATTERN, mchId, nonceStr, timestamp, serialNo, signature);
}
}
2 封装支付接口请求方法
/**
* 微信支付发送Https请求通用方法
*
* @param requestUrl
* @param requestMethod
* @param outputStr
* @param token
* @return
*/
public static JSONObject httpsRequestForWxPay(String requestUrl, String requestMethod, String outputStr, String token) {
JSONObject jsonObject = null;
StringBuffer buffer = new StringBuffer();
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = {new MyX509TrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
httpUrlConn.setRequestProperty("Content-type", "application/json");
httpUrlConn.setRequestProperty("Authorization", " WECHATPAY2-SHA256-RSA2048 " + token);
if ("GET".equalsIgnoreCase(requestMethod)) {
httpUrlConn.connect();
}
// 当有数据需要提交时
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
jsonObject = JSONObject.parseObject(buffer.toString());
} catch (ConnectException ce) {
ce.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return jsonObject;
}
本段代码可以根据自己的需要改为使用httpclient或者其他形式,本人习惯使用java自带的这套
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class MyX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
3 封装回调请求数据解密代码
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class WxPayAesUtilV3 {
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
private final byte[] aesKey;
public WxPayAesUtilV3(byte[] key) {
if (key.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
}
this.aesKey = key;
}
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}
4 主要工具类的封装,此工具类主要实现签名计算、公钥获取、解密响应体等
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.lg.wx.consts.ConstantsWx;
import com.lg.wx.pojo.SignVerify;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 微信V3支付签名工具类
*/
public class WxPaySignUtilV3 extends NoncestrUtil {
/**
* V3 SHA256withRSA 签名.
*
* @param method 请求方法 GET POST PUT DELETE 等
* @param canonicalUrl 例如 https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1
* @param timestamp 当前时间戳 因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
* @param nonceStr 随机字符串 要和TOKEN中的保持一致
* @param body 请求体 GET 为 "" POST 为JSON
* @param keyPair 商户API 证书解析的密钥对 实际使用的是其中的私钥
* @return the string
*/
public static String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair) throws Exception {
String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(keyPair.getPrivate());
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return new String(Base64.getEncoder().encode(sign.sign()));
}
/**
* jsapi计算签名
*
* @param appId
* @param timestamp
* @param nonceStr
* @param prepayId
* @param keyPair
* @return
* @throws Exception
*/
public static String signForJSApi(String appId, long timestamp, String nonceStr, String prepayId, KeyPair keyPair) throws Exception {
String signatureStr = Stream.of(appId, String.valueOf(timestamp), nonceStr, prepayId)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(keyPair.getPrivate());
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return new String(Base64.getEncoder().encode(sign.sign()));
}
/**
* 回调验签
*
* @param signVerify
* @return
* @throws Exception
* @para
*/
public static boolean responseSignVerify(SignVerify signVerify) throws Exception {
//获取最新的微信支付平台公钥
X509Certificate certificate = refreshCertificate(signVerify);
String signatureStr = responseSign(signVerify);
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initVerify(certificate);
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
boolean flag = signer.verify(Base64.getDecoder().decode(signVerify.getWechatpaySignature()));
return flag;
}
/**
* 构造验签名串.
*
* @param signVerify
* @return the string
*/
public static String responseSign(SignVerify signVerify) {
return Stream.of(signVerify.getWechatpayTimestamp(), signVerify.getWechatpayNonce(), signVerify.getBody())
.collect(Collectors.joining("\n", "", "\n"));
}
/**
* 解密响应体-取得公钥
*
* @param apiV3Key API V3 KEY API v3密钥 商户平台设置的32位字符串
* @param associatedData response.body.data[i].encrypt_certificate.associated_data
* @param nonce response.body.data[i].encrypt_certificate.nonce
* @param ciphertext response.body.data[i].encrypt_certificate.ciphertext
* @return the string
* @throws GeneralSecurityException the general security exception
*/
public static String decryptResponseBody(String apiV3Key, String associatedData, String nonce, String ciphertext) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
byte[] bytes;
try {
bytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException(e);
}
return new String(bytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 网络请求获取微信支付平台公钥
*
* @param pair
* @return
*/
public static JSONObject getPlatformPubKey(KeyPair pair, String serialNo, String mchId) {
String nonceStr = createNoncestr();
long timeStamp = System.currentTimeMillis() / 1000;
String signStr = "";
try {
signStr = sign("GET", "/v3/certificates", timeStamp, nonceStr, "", pair);
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
WxPayTokenUtilV3 tk = new WxPayTokenUtilV3();
String resToken = tk.token(mchId, nonceStr, timeStamp, serialNo, signStr);
HttpArgs args = HttpArgs.builder(ConstantsWx.WX_CERTIFICATES_URL).method("GET").outputStr(null).build();
JSONObject result = AdvancedUtil.httpsGetBeanForPay(args, JSONObject.class, resToken);
return result;
}
/**
* 获取最新的微信支付平台公钥并封装成证书
*
* @param signVerify
* @return
* @throws Exception
*/
public static X509Certificate refreshCertificate(SignVerify signVerify) throws Exception {
KeyPairFactory kp = new KeyPairFactory();
KeyPair keyPair = kp.createPKCS12(signVerify.getCertUrl(), "Tenpay Certificate", signVerify.getMchId());
//获取公钥信息
JSONObject json = getPlatformPubKey(keyPair, signVerify.getSerialNo(), signVerify.getMchId());
JSONArray arr = json.getJSONArray("data");
JSONObject child = arr.getJSONObject(0).getJSONObject("encrypt_certificate");
//associated_data
String associatedData = child.getString("associated_data");
//nonce
String nonce = child.getString("nonce");
//ciphertext
String ciphertext = child.getString("ciphertext");
String publicKey = decryptResponseBody(signVerify.getApiV3Key(), associatedData, nonce, ciphertext);
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X509");
} catch (CertificateException e1) {
e1.printStackTrace();
}
ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
X509Certificate certificate = null;
try {
certificate = (X509Certificate) cf.generateCertificate(inputStream);
} catch (CertificateException e) {
e.printStackTrace();
}
return certificate;
}
}
5 JSAPI和扫码支付具体实现代码封装
import com.alibaba.fastjson.JSONObject;
import com.lg.wx.consts.ConstantsWx;
import com.lg.wx.pojo.JsApiOrder;
import com.lg.wx.pojo.RefundsInfo;
import java.security.KeyPair;
import java.util.Date;
/**
* 微信支付v3版本工具类
*
* @author chenruiyin
* @version 1.0
*/
public class WxPayUtilV3 extends NoncestrUtil {
/**
* jsapi下单并取得支付json串
*
* @param order
* @return
* @throws Exception
*/
public static JSONObject jsApiCreateOrder(JsApiOrder order) throws Exception {
JSONObject json = new JSONObject();
json.put("appid", order.getAppId());
json.put("mchid", order.getMchId());
json.put("description", order.getDescription());
json.put("out_trade_no", order.getOutTradeNo());
json.put("notify_url", order.getNotifyUrl());
JSONObject childs = new JSONObject();
childs.put("total", order.getTotalPrice());
json.put("amount", childs);
JSONObject childs1 = new JSONObject();
childs1.put("openid", order.getOpenId());
json.put("payer", childs1);
json.put("attach", order.getAttach());
KeyPairFactory kp = new KeyPairFactory();
KeyPair res = kp.createPKCS12(order.getCertUrl(), ConstantsWx.KEY_ALIAS, order.getMchId());
String nonceStr = WxPaySignUtilV3.createNoncestr();
long timeStamp = System.currentTimeMillis() / 1000;
//WxPaySignUtilV3 ts=new WxPaySignUtilV3();
String signStr = "";
try {
signStr = WxPaySignUtilV3.sign("POST", "/v3/pay/transactions/jsapi", timeStamp, nonceStr, json.toString(), res);
} catch (Exception e) {
e.printStackTrace();
}
WxPayTokenUtilV3 tk = new WxPayTokenUtilV3();
//计算下单token
String resToken = tk.token(order.getMchId(), nonceStr, timeStamp, order.getSerialNo(), signStr);
//调用微信下单接口
HttpArgs args = HttpArgs.builder(ConstantsWx.TRANSACTIONS_JSAPI_URL).method("POST").outputStr(json.toString()).build();
JSONObject result = AdvancedUtil.httpsGetBeanForPay(args, JSONObject.class, resToken);
//计算签名
long timeStamp1 = new Date().getTime();
String nonceStr1 = WxPaySignUtilV3.createNoncestr();
String paySign = "";
try {
paySign = WxPaySignUtilV3.signForJSApi(order.getAppId(), timeStamp1, nonceStr1, "prepay_id=" + result.getString("prepay_id"), res);
} catch (Exception e) {
e.printStackTrace();
}
JSONObject payJson = new JSONObject();
payJson.put("appId", order.getAppId());
payJson.put("timeStamp", String.valueOf(timeStamp1));
payJson.put("nonceStr", nonceStr1);
payJson.put("package", "prepay_id=" + result.getString("prepay_id"));
payJson.put("signType", "RSA");
payJson.put("paySign", paySign);
return payJson;
}
/**
* 微信支付申请退款
*
* @param refund
* @return
* @throws Exception
*/
public static boolean refundsMoney(RefundsInfo refund) throws Exception {
JSONObject json = new JSONObject();
json.put("out_trade_no", refund.getOutTradeNo());
json.put("out_refund_no", String.valueOf(new Date().getTime()));
JSONObject child = new JSONObject();
child.put("refund", refund.getRefund());
child.put("total", refund.getTotal());
child.put("currency", refund.getCurrency());
json.put("amount", child);
KeyPairFactory kp = new KeyPairFactory();
KeyPair res = kp.createPKCS12(refund.getCertUrl(), ConstantsWx.KEY_ALIAS, refund.getMchId());
long timeStamp = System.currentTimeMillis() / 1000;
String signStr = "";
String nonceStr = createNoncestr();
try {
signStr = WxPaySignUtilV3.sign("POST", "/v3/refund/domestic/refunds", timeStamp, nonceStr, json.toString(), res);
} catch (Exception e) {
e.printStackTrace();
}
WxPayTokenUtilV3 tk = new WxPayTokenUtilV3();
//计算下单token
String resToken = tk.token(refund.getMchId(), nonceStr, timeStamp, refund.getSerialNo(), signStr);
JSONObject result = CommonUtil.httpsRequestForWxPay(ConstantsWx.REFUNDS_API_URL, "POST", json.toJSONString(), resToken);
if (null != result) {
return true;
}
return false;
}
/**
* native扫码支付
* @param order
* @return
* @throws Exception
*/
public static String nativeCreateOrder(JsApiOrder order) throws Exception {
JSONObject json = new JSONObject();
json.put("appid", order.getAppId());
json.put("mchid", order.getMchId());
json.put("description", order.getDescription());
json.put("out_trade_no", order.getOutTradeNo());
json.put("notify_url", order.getNotifyUrl());
JSONObject childs = new JSONObject();
childs.put("total", order.getTotalPrice());
json.put("amount", childs);
KeyPairFactory kp = new KeyPairFactory();
KeyPair res = kp.createPKCS12(order.getCertUrl(), ConstantsWx.KEY_ALIAS, order.getMchId());
String nonceStr = WxPaySignUtilV3.createNoncestr();
long timeStamp = System.currentTimeMillis() / 1000;
//WxPaySignUtilV3 ts=new WxPaySignUtilV3();
String signStr = "";
try {
signStr = WxPaySignUtilV3.sign("POST", "/v3/pay/transactions/native", timeStamp, nonceStr, json.toString(), res);
} catch (Exception e) {
e.printStackTrace();
}
WxPayTokenUtilV3 tk = new WxPayTokenUtilV3();
//计算下单token
String resToken = tk.token(order.getMchId(), nonceStr, timeStamp, order.getSerialNo(), signStr);
//调用微信下单接口
HttpArgs args = HttpArgs.builder(ConstantsWx.TRANSACTIONS_NATIVEAPI_URL).method("POST").outputStr(json.toString()).build();
JSONObject result = AdvancedUtil.httpsGetBeanForPay(args, JSONObject.class, resToken);
//System.out.println(result);
String codeUrl = result.getString("code_url");
return codeUrl;
}
JsApiOrder是支付重要的实体类,里面主要有调用支付接口需要的各种字段,封装起来目的是方便使用
import lombok.Data;
/**
* TODO
*
* @author Administrator
* @version 1.0
* @date 2021/3/30 15:02
*/
@Data
public class JsApiOrder {
/**
* appId
*/
private String appId;
/**
* 商户号
*/
private String mchId;
/**
* 订单描述
*/
private String description;
/**
* 订单号
*/
private String outTradeNo;
/**
* 支付成功回调
*/
private String notifyUrl;
/**
* 订单金额(单位为分)
*/
private Integer totalPrice;
/**
* 支付用户openId
*/
private String openId;
/**
* 诊所ID
*/
private Long clinicId;
/**
* 支付p12证书url
*/
private String certUrl;
/**
* 支付证书序列号
*/
private String serialNo;
/**
* 额外参数
*/
private String attach;
}
AdvancedUtil这个工具类则是把调用支付接口返回的数据二次封装,这一步可有可无,代码大致如下
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Builder;
@Builder
public class AdvancedUtil {
private String method = "get";
private String url;
/**
* 通过https请求获取返回json的数据,并把数据格式化为相应的bean
* @param httpArgs 请求参数列表使用方式 HttpArgs.builder().url("xxxurl").method("post").build()
* @param clazz 需要构建的数据对象
*/
public static <T> T httpsGetBean(HttpArgs httpArgs, Class<T> clazz) {
JSONObject jsonObject = CommonUtil.httpsRequest(httpArgs.getUrl(), httpArgs.getMethod(), httpArgs.getOutputStr());
//System.out.println("请求链接:"+httpArgs.getUrl());
//System.out.println("返回结果:"+jsonObject.toString());
return JSON.toJavaObject(jsonObject, clazz);
}
/**
* 通过https请求获取返回json的数据,并把数据格式化为相应的bean
*
* @param httpArgs 请求参数列表使用方式 HttpArgs.builder().url("xxxurl").method("post").build()
* @param clazz 需要构建的数据对象
* @return
* @Date 2021/03/13 15:32
*/
public static <T> T httpsGetBeanForPay(HttpArgs httpArgs, Class<T> clazz, String token) {
JSONObject jsonObject = CommonUtil.httpsRequestForWxPay(httpArgs.getUrl(), httpArgs.getMethod(), httpArgs.getOutputStr(), token);
return JSON.toJavaObject(jsonObject, clazz);
}
}
HttpArgs这个把代码调用改为连缀的形式,也可根据喜好选择用或者不用
import lombok.Builder;
import lombok.Data;
import java.util.Map;
/**
* @description: http接口请求参数, 构建请求参数
**/
@Data
public class HttpArgs {
public final static String GET = "GET";
public final static String POST = "POST";
private final static String UTF8 = "UTF-8";
private final static String GBK = "GBK";
private final static String DEFAULT_MEDIA_TYPE = "application/json";
private String url;
@Builder.Default
private String method = GET;
private String data;
private Map<String, Object> queryMap;
private String outputStr;
HttpArgs(final String url, final String method, final String data, final Map<String, Object> queryMap, final String outputStr) {
this.url = url;
this.method = method;
this.data = data;
this.queryMap = queryMap;
this.outputStr = outputStr;
}
public static HttpArgsBuilder builder(String url) {
return new HttpArgsBuilder(url);
}
public static class HttpArgsBuilder {
private String url;
private String method = GET;
private String data;
private Map<String, Object> queryMap;
private String outputStr;
HttpArgsBuilder(String url) {
this.url = url;
}
public HttpArgsBuilder url(final String url) {
this.url = url;
return this;
}
public HttpArgsBuilder method(final String method) {
this.method = method;
return this;
}
public HttpArgsBuilder queryMap(final Map<String, Object> queryMap) {
this.queryMap = queryMap;
return this;
}
public HttpArgsBuilder outputStr(final String outputStr) {
this.outputStr = outputStr;
return this;
}
public HttpArgs build() {
String method = this.method;
return new HttpArgs(this.url, method, this.data, this.queryMap, this.outputStr);
}
}
}
6 回调逻辑代码
@RequestMapping("/notify")
public void notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");//获取微信调用我们notify_url的返回信息
//System.out.println("result="+result);
JSONObject resJson = JSONObject.parseObject(result);
String wechatpayTimestamp = request.getHeader("Wechatpay-Timestamp");
String wechatpayNonce = request.getHeader("Wechatpay-Nonce");
String wechatpaySignature = request.getHeader("Wechatpay-Signature");
JSONObject resources = resJson.getJSONObject("resource");
//associated_data
String associated_data = resources.getString("associated_data");
//nonce
String nonce = resources.getString("nonce");
//ciphertext
String ciphertext = resources.getString("ciphertext");
JSONObject returnRes = new JSONObject();
WxPayAesUtilV3 aesUtil = new WxPayAesUtilV3(WxPayConfig.APIV3KEY.getBytes());
String decryptResources = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
//System.out.println("decryptResources=" + decryptResources);
//获取订单号
JSONObject decryptResourcesJson = JSONObject.parseObject(decryptResources);
String out_trade_no = decryptResourcesJson.getString("out_trade_no");
String trade_state = decryptResourcesJson.getString("trade_state");
//获取订单金额信息
JSONObject ammountJson = decryptResourcesJson.getJSONObject("amount");
//总金额
Integer total = ammountJson.getInteger("total");
//用户支付金额
Integer payerTotal = ammountJson.getInteger("payer_total ");
SignVerify signVerify = new SignVerify();
signVerify.setSerialNo(WxPayConfig.SERIALNO);
signVerify.setApiV3Key(WxPayConfig.APIV3KEY);
signVerify.setMchId(WxPayConfig.MCHID);
signVerify.setCertUrl(WxPayConfig.CERTURL);
signVerify.setWechatpaySignature(wechatpaySignature);
signVerify.setWechatpayTimestamp(wechatpayTimestamp);
signVerify.setWechatpayNonce(wechatpayNonce);
signVerify.setBody(result);
try {
if (WxPaySignUtilV3.responseSignVerify(signVerify)) {
if ("SUCCESS".equalsIgnoreCase(trade_state)) {
//此处可根据实际情况编写处理逻辑
//例如修改订单状态,积分操作,发消息等等
returnRes.put("code", "SUCCESS");
returnRes.put("message", "成功");
} else {
returnRes.put("code", "500");
returnRes.put("message", "交易未完成");
}
} else {
returnRes.put("code", "500");
returnRes.put("message", "签名校验失败");
}
} catch (Exception e) {
e.printStackTrace();
}
response.getWriter().write(returnRes.toString());
}
7 公钥私钥获取
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
/**
* KeyPairFactory
*
* @author dax
* @since 13:41
**/
public class KeyPairFactory {
private final Object lock = new Object();
private KeyStore store;
/**
* 获取公私钥.
*
* @param keyPath the key path
* @param keyAlias the key alias
* @param keyPass password
* @return the key pair
*/
public KeyPair createPKCS12(String keyPath, String keyAlias, String keyPass) throws Exception {
//ClassPathResource resource = new ClassPathResource(keyPath);
URL url = new URL(keyPath);
HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod("GET");
httpUrlConn.connect();
InputStream in = httpUrlConn.getInputStream();
//in.close();
//httpUrlConn.disconnect();
char[] pem = keyPass.toCharArray();
try {
synchronized (lock) {
if (store == null) {
synchronized (lock) {
store = KeyStore.getInstance("PKCS12");
store.load(in, pem);
}
}
}
X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
certificate.checkValidity();
// 证书的序列号 也有用
String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
// 证书的 公钥
PublicKey publicKey = certificate.getPublicKey();
// 证书的私钥
PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);
return new KeyPair(publicKey, storeKey);
} catch (Exception e) {
throw new IllegalStateException("Cannot load keys from store: " + keyPath, e);
}
}
}
此处秘钥获取需要使用商户平台的证书,这里把p12证书放到的oss上然后通过get请求获取
备注:微信支付的证书别名为Tenpay Certificate写错代码将会报错
/**
* 支付p12证书别名
*/
String KEY_ALIAS = "Tenpay Certificate";