调取微信支付需要有商户号,商户密钥,以及证书
有以上三样之后,才可以进行支付调用
1.声明商户号,商户密钥,商户证书序列号
X509Certificate 证书(Certificate,也称public-keycertificate)
它是用某种签名算法对某些内容(比如公钥)进行数字签名后得到的、可以用来当成信任关系中介的数字凭证。证书发行机构通过发行证书告知证书使用者或实体其公钥(public-key)以及其它一些辅助信息。
微信支付证书放在resources下,通过微信提供的工具类PemUtil实现证书解析,解析证书后,可以获得商户证书序列号
private static final String apiV3Key ="密钥";//密钥
private static final String merchantId = "商户号";// 商户号
private static final String merchantSerialNumber = WxUtils.getMerchantSerialNumber(); // 商户证书序列号
//获取序列号方式
public static String getMerchantSerialNumber(){
String merchantSerialNumber = "";
try{
X509Certificate X509 = getX509();
merchantSerialNumber = X509.getSerialNumber().toString(16).toUpperCase();
}catch(Exception e){
e.printStackTrace();
}
return merchantSerialNumber;
}
//获取X509Certificate证书
public static X509Certificate getX509() throws Exception{
ClassPathResource resource = new ClassPathResource("**apiclient_cert.pem**");//微信支付证书
InputStream certid = resource.getInputStream();
X509Certificate X509 = PemUtil.loadCertificate(certid);
return X509;
}
2.微信提供API中PemUtil
微信提供的API PemUtil中,实现了解析密钥,解析证书的方法
public static PrivateKey loadPrivateKey(String privateKey) {
privateKey = privateKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
public static PrivateKey loadPrivateKey(InputStream inputStream) {
ByteArrayOutputStream os = new ByteArrayOutputStream(2048);
byte[] buffer = new byte[1024];
String privateKey;
try {
for (int length; (length = inputStream.read(buffer)) != -1; ) {
os.write(buffer, 0, length);
}
privateKey = os.toString("UTF-8");
} catch (IOException e) {
throw new IllegalArgumentException("无效的密钥", e);
}
return loadPrivateKey(privateKey);
}
public static X509Certificate loadCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
} catch (CertificateException e) {
throw new RuntimeException("无效的证书", e);
}
}
3.调取接口
成功调取微信支付的第一步,生成prepay_id(预支付交易会话标识),有效期为两小时,是后续调取接口的凭证
该步需要进行一次验签,通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
必备参数:
appid ==>由微信生成的应用ID,全局唯一。
mchid ==>商户号
description ==>商品描述(String类型,1-127字符以内)
out_trade_no ==>订单号,唯一标识,建议使用UUID
notify_url ==>通知地址,异步调取
返回值bodyAsString 需要转为JSON字符串后获取里面的prepay_id
/**
* 调取微信支付接口生成预订单
* @param url
* @param objectMapper
* @param rootNode
* @return
*/
public static String getHttpClient(String url, ObjectMapper objectMapper,ObjectNode rootNode){
String bodyAsString= "";
try{
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
objectMapper.writeValue(bos, rootNode);//传入参数
PrivateKey merchantPrivateKey =getPrivateKey();
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
String merchantSerialNumber = getMerchantSerialNumber();
Verifier verifier = getVerifier();
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
CloseableHttpClient httpClient = builder.build();
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response.getEntity());
JSONObject sessionData = JSON.parseObject(bodyAsString);
bodyAsString = sessionData.getString("prepay_id");
}catch (Exception e) {
e.printStackTrace();
}
return bodyAsString ;
}
4.获取私钥
与解析证书相同,微信提供的工具类PemUtil 可以解析私钥
public static PrivateKey getPrivateKey() throws Exception{
ClassPathResource privteKeyFile = new ClassPathResource("cert/apiclient_key.pem");
InputStream keyis = privteKeyFile.getInputStream();
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(keyis);
return merchantPrivateKey;
}
5.Verifier
自动更新证书
public static Verifier getVerifier() throws Exception{
PrivateKey merchantPrivateKey;
merchantPrivateKey =getPrivateKey();
//不需要传入微信支付证书,将会自动更新
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
Verifier verifier = certificatesManager.getVerifier(merchantId);
return verifier;
}
6.小程序调取微信支付所需要的参数处理
微信支付开发文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
必备参数:
appId ==>商户申请的小程序对应的appid,由微信支付生成,可在小程序后台查看
timeStamp ==>时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)
nonceStr ==>随机字符串,不长于32位
package ==> 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***signType==>签名类型,默认为RSA,仅支持RSA。
paySign ==>签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值
注:签名顺序不可以乱,这个appId是大写,获取prepay_id标识时传递的appid是小写的
构造签名串的顺序
签名串一共有四行,每一行为一个参数。行尾以\n(换行符,ASCII编码值为0x0A)结束,包括最后一行。
如果参数本身以\n结束,也需要附加一个\n示例:
小程序appId
时间戳(timeStamp)
随机字符串 (nonceStr)
订单详情扩展字符串(package )
/**
* 调取微信支付,生成签名
* @param param
* @return
*/
public static R Pay(Map<String, String> param){
R result = new R();
String url = "微信支付接口,即 小程序调起支付API";
String currency = "CNY";//缴费金额类型
String appid ="APPID";//appid
String regDiscount = param.get("regDiscount");//金额
String openId = param.get("openId");//openID
String tradeno = param.get("tradeno");//票据号
String notify_url = "回调地址";//回调地址
String description = param.get("description");//描述
int total = new BigDecimal(regDiscount).multiply(new BigDecimal(100)).intValue();//缴费金额==>金额以分为单位
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid",merchantId)
.put("appid", appid)
.put("description", description)
.put("notify_url", notify_url)
.put("out_trade_no", tradeno);
rootNode.putObject("amount")
.put("total", total)
.put("currency", currency);
rootNode.putObject("payer")
.put("openid", openId);
String prepay_id = WxUtils.getHttpClient(url,objectMapper,rootNode);//预支付交易会话标识
long timeStamp = System.currentTimeMillis() / 1000;//时间戳
String nonceStr = UUID;//随机字符串
String content = appid+ "\n"+timeStamp+"\n"+nonceStr+"\nprepay_id="+prepay_id+"\n";//拼好的内容
String paySign = WxUtils.signBySHA256WithRSA(content);//生成签名
result.put("timeStamp", timeStamp+"");
result.put("nonceStr",nonceStr);
result.put("package","prepay_id="+prepay_id);
result.put("signType","RSA");
result.put("paySign",paySign);
return result;
}
生成RSA签名
V3只支持RSA签名方式
/**
* rsa2Sign:rsa2签名
* @author zhangh
* @param content
* @return
*/
public static String signBySHA256WithRSA(String content){
String paySign = "";
try{
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey());
sign.update(content.getBytes(StandardCharsets.UTF_8));
paySign = Base64Utils.encodeToString(sign.sign());
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
}catch (Exception e) {
e.printStackTrace();
}
return paySign;
}
7.微信回调接口—> notify_url 回调地址
首先在控制层处理请求头
其次回调内容需解析
最后通过notification.getEventType() == ‘TRANSACTION.SUCCESS’ 判断是否支付成功
@ResponseBody
@RequestMapping(value = "getPaySign", method = RequestMethod.POST)
public R getPaySign(HttpServletRequest request, HttpServletResponse response){
//回调通知的验签与解密
String wechatPaySerial = request.getHeader(WECHAT_PAY_SERIAL);
String nonce = request.getHeader(WECHAT_PAY_NONCE); // 请求头Wechatpay-Nonce
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP); // 请求头Wechatpay-Timestamp
String signature = request.getHeader(WECHAT_PAY_SIGNATURE); // 请求头Wechatpay-Signature
String body = HttpUtils.readData(request);
Map<String, String> param = new HashMap<>();
param.put("wechatPaySerial",wechatPaySerial);
param.put("nonce",nonce);
param.put("timestamp",timestamp);
param.put("signature",signature);
param.put("body",body);
//处理订单
R result = registAppletService.getPaySign(param);
return result;
}
/**
* 支付解析回调内容
* @param param
* @return
*/
public static Notification analysisNotification(Map<String, String> param){
try{
String wechatPaySerial = param.get("wechatPaySerial");
String nonce = param.get("nonce"); // 请求头Wechatpay-Nonce
String timestamp = param.get("timestamp"); // 请求头Wechatpay-Timestamp
String signature =param.get("signature"); // 请求头Wechatpay-Signature
String body = param.get("body");
Verifier verifier = WxUtils.getVerifier();
// 构建request,传入必要参数
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);
String eventType = notification.getEventType();
logger.info("notification报文:"+notification);
return notification;
}catch(Exception e){
e.printStackTrace();
return null;
}
}
8.查询订单详情
查询订单详情也需要有签名处理
/**
* 通过商户号和票号查询微信支付单
* @param out_trade_no
* @return
*/
public static String HttpClientSel(String out_trade_no){
String bodyAsString = "";
try{
PrivateKey merchantPrivateKey =WxUtils.getPrivateKey();
String merchantSerialNumber = WxUtils.getMerchantSerialNumber();
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+out_trade_no+"?mchid="+merchantId);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
Verifier verifier = WxUtils.getVerifier();
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
CloseableHttpClient httpClient = builder.build();
CloseableHttpResponse response = httpClient.execute(httpGet);
bodyAsString = EntityUtils.toString(response.getEntity());
}catch(Exception e){
e.printStackTrace();
}
return bodyAsString;
}