1.申请商户获取参数或文件
微信小程序appid:xxxx
商户API证书私钥文件:xxxx(微信支付)
商户号:xxxx (微信支付)
商户API证书序列号:xxxx (微信支付)
商户API v2密钥:xxxx(自定义)
商户API v3密钥:xxxx(自定义)
微信平台证书文件:xxxx (自己生成或自动生成)
微信小程序开发密钥:xxxx(小程序)
平台证书不是API证书:需5年更换一次
下载jar包https://github.com/wechatpay-apiv3/CertificateDownloader
https://github.com/wechatpay-apiv3/CertificateDownloader/releases 2.application.properties配置
wechat.pay.app_id= xxxx # 微信小程序appid
wechat.pay.private_key_path=xxxx.pem #商户API证书私钥文件路径
wechat.pay.merchant_id= xxxx #商户号
wechat.pay.merchant_serial_number= xxxx #商户API证书序列号
wechat.pay.api_v2_key= xxxx #商户API v2密钥
wechat.pay.api_v3_key= xxxx #商户API v3密钥
wechat.pay.wechat_certificate_path= xxxx.pem #微信平台证书文件路径
wechat.pay.app_select=xxxx #微信小程序开发密钥
wechat.pay.notify_url=https://xxxxx.com:port/succeed.atcion#支付成功回调 https开头
3.maven pom
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
4.微信支付属性配置
package com.mt.applets.config;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.io.*;
import java.security.PrivateKey;
import java.security.cert.*;
@Data
@Component
@PropertySource("classpath:application.properties")
public class WxPayProperties {
/**
*小程序appId
*/
@Value("${wechat.pay.app_id}")
private String appId;
/**
* 私钥
*/
@Value("${wechat.pay.private_key_path}")
private String privateKeyPath;
/**
* 商户号
*/
@Value("${wechat.pay.merchant_id}")
private String merchantId;
/**
* 商户证书序列号
* */
@Value("${wechat.pay.merchant_serial_number}")
private String merchantSerialNumber;
/**
* apiV3密钥
*/
@Value("${wechat.pay.api_v3_key}")
private String apiV3Key;
/**
* 平台证书路径
*/
@Value("${wechat.pay.wechat_certificate_path}")
private String wechatCertificatePath;
/**
* 回调路径
*/
@Value("${wechat.pay.notify_url}")
private String notifyUrl;
/**
* App密钥
*/
@Value("${wechat.pay.app_select}")
private String appSelect;
/**
* @Decription 获取商户的私钥
* @Return java.security.PrivateKey
*/
public PrivateKey getPrivateKey() {
try {
return PemUtil.loadPrivateKey(
new FileInputStream(this.privateKeyPath));
} catch (FileNotFoundException e) {
//抛出异常,并把错误文件继续向上抛出
throw new RuntimeException("私钥文件不存在", e);
}
}
public X509Certificate getCertificate() throws IOException {
InputStream fis = new FileInputStream(this.wechatCertificatePath);
try (BufferedInputStream bis = new BufferedInputStream(fis)) {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
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);
}
}
}
5.支付相关业务,非自身业务
package com.mt.applets.service.impl;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mt.api.dto.OrderPayInfo;
import com.mt.api.util.StringUtils;
import com.mt.applets.config.WxPayProperties;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
public class PayServiceImpl {
@Autowired
private WxPayProperties wxPayProperties;
private Verifier verifier;
private CloseableHttpClient httpClient;
private final Logger logger= LoggerFactory.getLogger(PayServiceImpl.class);
@PostConstruct
private void init() throws IOException, HttpCodeException, GeneralSecurityException, NotFoundException {
String merchantId= wxPayProperties.getMerchantId();
String merchantSerialNumber= wxPayProperties.getMerchantSerialNumber();
PrivateKey merchantPrivateKey =wxPayProperties.getPrivateKey();
String apiV3Key =wxPayProperties.getApiV3Key();
// 平台证书管理器
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
apiV3Key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
verifier = certificatesManager.getVerifier(merchantId);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
httpClient = builder.build();
}
/**
* 签名
*/
public String sign(byte[] message) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey merchantPrivateKey = wxPayProperties.getPrivateKey();
sign.initSign(merchantPrivateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
//创建订单,返回预处理订单
public CloseableHttpResponse createPayOrder(OrderPayInfo orderPayInfo) throws IOException {
// 请求body参数
String billNo= orderPayInfo.getBillNo();
Integer price= orderPayInfo.getPrice();
String description= orderPayInfo.getDesc();
String openId= orderPayInfo.getOpenId();
String merchantId = wxPayProperties.getMerchantId();
String appId = wxPayProperties.getAppId();
String notifyUrl = wxPayProperties.getNotifyUrl();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid",merchantId)
.put("appid", appId)
.put("description", description)
.put("notify_url", notifyUrl)
.put("out_trade_no", billNo);
rootNode.putObject("amount")
.put("total", price);
rootNode.putObject("payer")
.put("openid", openId);
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString(StandardCharsets.UTF_8), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response.getEntity());
logger.info(bodyAsString);
response.close();
return response;
}
//回调解析,获取支付回调数据
public String notificationHandler(String nonce,String timestamp,String signature,String body) throws Exception {
X509Certificate certificate = wxPayProperties.getCertificate();
String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
String apiV3Key = wxPayProperties.getApiV3Key();
// 构建request,传入必要参数
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(serialNo)
.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中获取解密报文
return notification.getDecryptData();
}
//获取小程序登录者的appID
public String getOpenId(String code) {
String secret=wxPayProperties.getAppSelect();
String appId=wxPayProperties.getAppId();
//appId和secret是开发者分别是小程序ID和小程序密钥,开发者通过微信公众平台-》设置-》开发设置就可以直接获取,
String url="https://api.weixin.qq.com/sns/jscode2session?appid="
+appId+"&secret="+secret+"&js_code="+code+"&grant_type=authorization_code";
try{
String res = null;
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpGet httpget = new HttpGet(url);
CloseableHttpResponse response;
// 配置信息
RequestConfig requestConfig = RequestConfig.custom()
// 设置连接超时时间(单位毫秒)
.setConnectTimeout(5000)
// 设置请求超时时间(单位毫秒)
.setConnectionRequestTimeout(5000)
// socket读写超时时间(单位毫秒)
.setSocketTimeout(5000)
// 设置是否允许重定向(默认为true)
.setRedirectsEnabled(false).build();
// 将上面的配置信息 运用到这个Get请求里
httpget.setConfig(requestConfig);
// 由客户端执行(发送)Get请求
response = httpClient.execute(httpget);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null) {
res = EntityUtils.toString(responseEntity);
logger.info(res);
}
// 释放资源
httpClient.close();
response.close();
JSONObject jo = (JSONObject) JSONUtil.parse(res);
if(jo.get("openid")==null||StringUtils.isEmpty((String) jo.get("openid"))){
throw new RuntimeException("获取openId失败");
}
return (String) jo.get("openid");
}catch (Exception e) {
throw new RuntimeException("获取openId失败", e);
}
}
}
6.调用试例(非完整)
//调用支付试例,返回给前端调起支付
public WeChatPayResult generateOrderToPay(String desc,Integer price,String billNo,String code) throws IOException {
OrderPayInfo orderPayInfo = new OrderPayInfo();
orderPayInfo.setDesc(desc);
orderPayInfo.setPrice(price);
orderPayInfo.setBillNo(billNo);
String openId=payService.getOpenId(code);
orderPayInfo.setOpenId(openId);
try (CloseableHttpResponse response = payService.createPayOrder(orderPayInfo)) {
String appId = wxPayProperties.getAppId();
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200 || statusCode == 204) {
String entity = EntityUtils.toString(response.getEntity());
JSONObject parse = JSONUtil.parseObj(entity);
Object prepayIdObj = parse.get("prepay_id");
String prepayId = String.valueOf(prepayIdObj);
WeChatPayResult weChatPayResult = new WeChatPayResult();
weChatPayResult.setPrepayId(prepayId);
String nonceStr = StringUtils.getUUID(20);
weChatPayResult.setNonceStr(nonceStr);
long timestamp = System.currentTimeMillis() / 1000;
weChatPayResult.setTimeStamp(timestamp);
weChatPayResult.setSignType("RSA");
String str = appId + "\n" +
timestamp + "\n" +
nonceStr + "\n" +
"prepay_id=" + prepayId +
"\n";
String sign = payService.sign(str.getBytes(StandardCharsets.UTF_8));
weChatPayResult.setPaySign(sign);
weChatPayResult.setAppId(appId);
return weChatPayResult;
} else {
logger.error(EntityUtils.toString(response.getEntity()));
}
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
//回调试例
public boolean confirmOrder(HttpServletRequest httpServletRequest) {
String serial = httpServletRequest.getHeader("Wechatpay-Serial");
String nonceStr = httpServletRequest.getHeader("Wechatpay-Nonce");
String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");
String signature = httpServletRequest.getHeader("Wechatpay-Signature");
if(StringUtils.isAnyEmpty(signature,nonceStr,timestamp,serial)){
logger.error("微信回调请求头参数缺失");
return false;
}
try {
String bodyStr = this.getBodyStr(httpServletRequest);
String result = payService.notificationHandler(nonceStr, timestamp, signature, bodyStr);
JSONObject jsonObject = JSONUtil.parseObj(result);
if("SUCCESS".equals(jsonObject.get("trade_state"))){
Object successTime = jsonObject.get("success_time");
JSONObject payer = (JSONObject)jsonObject.get("payer");
Object openid = payer.get("openid");
Object outTradeNo = jsonObject.get("out_trade_no");
//自身业务
}else{
logger.error("支付失败");
}
}catch (Exception e){
logger.error(e.getMessage());
return false;
}
return true;
}
//获取请求体串
public String getBodyStr(HttpServletRequest request) throws IOException {
BufferedReader br = request.getReader();
String str;
StringBuilder wholeStr = new StringBuilder();
while((str = br.readLine()) != null){
wholeStr.append(str);
}
return wholeStr.toString();
}