要实现微信支付,必须要有一个载体,目前可选的载体有公众号、开发平台APP和小程序,每个载体都有一个appId。原来的微信支付V2版本,现在需要用的是V3版本,过程踩坑无数,特特将接入流程记录供其他记录员阅读。注:微信支付V3版本都需要用到证书,请参考
1、引入微信官方jar包
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
2、自动注入处理类
@Configuration
@Slf4j
public class WechatPayBeanConfig {
@Autowired
WechatConfig wechatConfig;
/**
* 加载秘钥
*
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getCertificatesVerifier() throws IOException {
// 使用定时更新的签名验证器,不需要传入证书
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
new WechatPay2Credentials(wechatConfig.getWechatPayMchId(),
new PrivateKeySigner(wechatConfig.wechatPayCertSerialNo,
WechatPayV3Util.getPrivateKey())),
wechatConfig.getWechatPayMchKey().getBytes(StandardCharsets.UTF_8));
return verifier;
}
@Bean("wechatPayClient")
public CloseableHttpClient getWechatPayClient(ScheduledUpdateCertificatesVerifier verifier) throws IOException {
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(wechatConfig.getWechatPayMchId(),wechatConfig.wechatPayCertSerialNo , WechatPayV3Util.getPrivateKey())
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
}
public class WechatPayV3Util {
private static final Logger logger = LoggerFactory.getLogger(WechatPayV3Util.class);
public static String random(int len) {
Random r = new Random();
String[] chars = new String[]{"a", "b", "c", "d", "e", "f", "g", "l", "m", "n", "o", "p", "q", "r", "s", "t",
"u", "v", "w", "x", "y", "z"};
StringBuilder str = new StringBuilder();
for (int i = 0; i < len; i++) {
int ran1 = r.nextInt(chars.length);
str.append(chars[ran1]);
}
return str.toString().toUpperCase();
}
@SneakyThrows
public static String sign(byte[] message) {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey());
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
@SneakyThrows
public static PrivateKey getPrivateKey() {
WechatConfig wechatConfig = MySpringContext.getContext().getBean(WechatConfig.class);
String content = new String(Files.readAllBytes(Paths.get(wechatConfig.getWechatPayKeyFileName())), StandardCharsets.UTF_8);
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey finalPrivateKey = kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
return finalPrivateKey;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
}
二、生成JSAPI预支付单号
public String requestPrePayId(RequestPrePayDto requestPrePayDto) {
AdvancePayDto advancePayDto = new AdvancePayDto();
advancePayDto.setAppid("公众号appId");
advancePayDto.setMchid("商户mchID");
advancePayDto.setDescription("商品描述");
advancePayDto.setOutTradeNo("商户单号");
advancePayDto.setNotifyUrl("https://");//回调地址
// 订单金额信息
AdvanceAmountDto advanceAmountDto = new AdvanceAmountDto();
advanceAmountDto.setTotal(1);
advancePayDto.setAmount(advanceAmountDto);
// 支付者信息
AdvancePayerDto advancePayerDto = new AdvancePayerDto();
advancePayerDto.setOpenid(requestPrePayDto.getPaOpenId());
advancePayDto.setPayer(advancePayerDto);
HttpPost postRequest = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
postRequest.addHeader("Accept", "application/json");
postRequest.addHeader("Content-Type", "application/json");
//获取微信PayHttpClient,可以@Autowrite
CloseableHttpClient wechatPayClient = MySpringContext.getApplicationContext().getBean("wechatPayClient", CloseableHttpClient.class);
postRequest.setEntity(new StringEntity(JsonUtil.toJsonString(advancePayDto)));
try (CloseableHttpResponse response = wechatPayClient.execute(postRequest)) {
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
return responseStr;
} catch (Exception ex) {
logger.error("生成预支付订单失败:{}", ex.getMessage());
throw new GlobalApiException("生成预支付订单失败");
}
}
三、支付回调
1、签名验证
default void checkPaySign(HttpServletRequest request, String bodyContent) {
ScheduledUpdateCertificatesVerifier verifier = MySpringContext.getApplicationContext().getBean(ScheduledUpdateCertificatesVerifier.class);
//随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");
logger.info("Wechatpay-Nonce:{}", nonceStr);
//微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");
logger.info("Wechatpay-Signature:{}", signature);
//证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");
logger.info("Wechatpay-Serial:{}", serialNo);
//时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
logger.info("Wechatpay-Timestamp:{}", timestamp);
//构造签名串
//应答时间戳\n
//应答随机串\n
//应答报文主体\n
String signStr = Stream.of(timestamp, nonceStr, bodyContent).collect(Collectors.joining("\n", "", "\n"));
logger.info("signStr:{}", signStr);
if (!verifier.verify(serialNo, signStr.getBytes(StandardCharsets.UTF_8), signature)) {
logger.error("签名验证失败");
throw new GlobalApiException("签名验证失败");
}
}
2、数据解密
default PrePayInfo callBack(WechatPayNotifyInfoDto wechatPayNotifyInfoDto) {
PrePayInfoDao prePayInfoDao = MySpringContext.getBean(PrePayInfoDao.class);
WechatConfig wechatConfig = MySpringContext.getBean(WechatConfig.class);
AesUtil aesUtil = new AesUtil(wechatConfig.getWechatPayMchKey().getBytes(StandardCharsets.UTF_8));
String str = aesUtil.decryptToString(wechatPayNotifyInfoDto.getResource().getAssociatedData().getBytes(StandardCharsets.UTF_8),
wechatPayNotifyInfoDto.getResource().getNonce().getBytes(StandardCharsets.UTF_8),
wechatPayNotifyInfoDto.getResource().getCiphertext());
HashMap<String, Object> resourceObj = (HashMap<String, Object>) JsonUtil.parseJsonObject(str, HashMap.class);
logger.info("解密成功:{}", str);
String outTradeNo = (String) resourceObj.get("out_trade_no");
String payState = (String) resourceObj.get("trade_state");
}