记录APP(后台)接入微信支付APIV3
一 前言
在如今的时代大背景下,手机支付成了生活中不可缺少的一环。其中的手机支付两巨头,当属微信和支付宝了,当我们想要在自己的项目吸引和留住用户,其中便捷支付必不可少,微信和支付宝支付就成为了最基础且最便捷的支付选项了。
其中支付宝支付,文档和SDK都比较简单明了,支付和回调验证都有demo进行示例,因此通过官方文档能迅速上手。微信支付相对于支付宝支付,多了很多加密解密,加签和验签的操作,流程上会比较复杂一些,因此记录一次接入微信支付APIV3的流程,第一个加深印象,其次希望能提供给其他用户一些小小的帮助。本次支付后台使用java实现。
二 接入准备
接入准备可以参考微信官方文档,APP支付接入前准备
其中需要准备好的内容如下,这些都放在了配置文件中。
商户号 merchantId
API v3密钥 apiV3Key
商户API证书的证书序列号 merchantSerialNumber
以上三项都可以在账户中心找到
商户API私钥 merchantPrivateKey
下载到本地的商户证书文件apiclient_key.pem ,为了方便,直接存在了properties文件中,将每个换行符替换成\n,为了节约空间,省略掉中间部分如下
-----BEGIN CERTIFICATE-----\n MIID8DCCAtigAwIBAgIUa7EnE8gKbMS5KaXKozPCbtsaeDwwDQYJKoZIhvcNAQEL\nBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTCl.........\nrq8KQU3zfyKS/TCQfiuCEfX1DKmuyaqLm28Y9+8v/mB+KKJF9NYlht+qsc63roOy\nKbeDiQ==\n-----END CERTIFICATE-----
AppId 已上线的app对应的id
三 接入微信支付
微信支付的所有请求,都选择用其封装好的httpclient,因此需要进行httpclient配置。参考APP支付开发指引
1. 搭建和配置开发环境
1.wechatpay-apache-httpclient 版本
版本选用最新版,方便省事。
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
2.httpclient配置
配置WeChatPayProperties
@Data //匹配配置文件中前缀为 wechatpay 的配置项 @ConfigurationProperties("wechatpay") public class WeChatPayProperties { /** * 商户号 */ private String merchantId; /** * remark:API v3密钥 */ private String apiV3Key; /** * remark:商户API证书的证书序列号 */ private String merchantSerialNumber; /** * remark:商户API私钥 */ private String merchantPrivateKey; }
配置httpclient
//开启WeChatPayProperties的配置功能
@EnableConfigurationProperties(WeChatPayProperties.class)
@Configuration
public class WeChatPayConfig {
@Bean
public CloseableHttpClient getCloseableHttpClient(WeChatPayProperties weChatPayProperties) throws IOException, HttpCodeException, GeneralSecurityException, NotFoundException {
//该构建代码参考官网 https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(weChatPayProperties.getMerchantId(), new WechatPay2Credentials(weChatPayProperties.getMerchantId(),
new PrivateKeySigner(weChatPayProperties.getMerchantSerialNumber(),
PemUtil.loadPrivateKey(
new ByteArrayInputStream(weChatPayProperties.getMerchantPrivateKey().getBytes("utf-8"))))),
weChatPayProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(weChatPayProperties.getMerchantId());
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(weChatPayProperties.getMerchantId(),
weChatPayProperties.getMerchantSerialNumber(),
PemUtil.loadPrivateKey(
new ByteArrayInputStream(weChatPayProperties.getMerchantPrivateKey().getBytes("utf-8"))))
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
}
2. 后台处理支付流程
只包含后台的处理流程,省略app端的支付过程。。。。
1. 生成待支付订单
下单参考APP下单API
//获取支付订单 goodsName 商品名 orderPrice 订单价格(单位为分) orderId 订单id notifyUrl 回调通知的地址(本项目的)
//在业务代码中有专门处理异常的地方,所以在生成支付订单的时候如果有异常直接抛出
public String getWeChatPayOrderStr(String goodsName, Integer orderPrice,Long orderId,String notifyUrl) throws Exception {
//构建请求
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
//构建支付参数,此处的参数都是参考下单API中的请求参数进行构建,也可以通过Map构建更简单,每项参数文档上都很清楚,此处不做过多赘述
WechatPayAmount wechatPayAmount = new WechatPayAmount();
wechatPayAmount.setCurrency("CNY");
wechatPayAmount.setTotal(orderPrice);
WechatPayParam wechatPayParam = new WechatPayParam();
wechatPayParam.setAppid(appId);
wechatPayParam.setMchid(merchantId);
wechatPayParam.setDescription(goodsName);
wechatPayParam.setNotify_url(notifyUrl);
wechatPayParam.setOut_trade_no(orderId + "");
wechatPayParam.setAmount(wechatPayAmount);
StringEntity stringEntity = new StringEntity(JSON.toJSONString(wechatPayParam));
//构件post请求
HttpPost post = new HttpPost(uriBuilder.build());
post.setEntity(stringEntity);
post.addHeader("Accept", "application/json");
post.addHeader("Content-Type", "application/json");
//使用httpClient发起请求,将请求的返回值处理成json字符串返回
HttpResponse execute = httpClient.execute(post);
String bodyAsString = EntityUtils.toString(execute.getEntity());
return bodyAsString;
}
2. 支付订单签名
//将支付订单处理生成签名,然后返回给app端
public WeChatPayDto getWeChatPaySign(String bodyAsString) throws Exception {
JSONObject jsonObject = JSON.parseObject(bodyAsString, JSONObject.class);
//将获取到的prepay_id 进行 加密
StringBuilder sb = new StringBuilder();
sb.append(appId+"\n");
//时间戳,秒 DateUtil 为hutool下的工具
String timeStamp = DateUtil.currentSeconds() + "";
sb.append(timeStamp + "\n");
//随机字符串,采用的开源雪花算法ID生成工具 YitIdHelper https://gitee.com/yitter/idgenerator
String nonceStr = YitIdHelper.nextId() + "";
sb.append(nonceStr + "\n");
String repayId = jsonObject.getString("prepay_id");
sb.append(repayId + "\n");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
// 进行签名服务
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(merchantPrivateKey);
signature.update(sb.toString().getBytes("utf-8"));
byte[] signedData = signature.sign();
String signData = Base64.getEncoder().encodeToString(signedData);
//weChatPayDto是app端调用支付需要的请求参数
//appId partnerId(商家id) prepayId(后台生成的待支付订单) packageValue nonceStr timeStamp sign(生成的签名)
WeChatPayDto weChatPayDto = new WeChatPayDto();
weChatPayDto.setAppId(WechatPayUtil.appId);
weChatPayDto.setPartnerId(WechatPayUtil.mchId);
weChatPayDto.setNonceStr(nonceStr);
weChatPayDto.setPrepayId(repayId);
weChatPayDto.setTimeStamp(timeStamp);
weChatPayDto.setSign(signData);
return weChatPayDto;
}
3. 支付通知
app端支付成功后,微信会通知到我们提供的回调接口,参考支付通知 。 在项目中提供的回调接口,建议写法如下,这样写能直接获取到通知数据,不用再通过其他处理去获取通知数据。
@ApiOperation(value = "微信支付订单回调接口")
@PostMapping("/wechatPayCallback")
public WeChatReply wechatPayCallback(
HttpServletRequest request, @RequestBody Map<String,Object> body){
return orderService.wechatPayCallback(request,body);
}
//WeChatReply(通知应答体) code 和 message
3.1 通知验签
//验证签名
public boolean checkSign(HttpServletRequest request, Map<String,Object> requestBody) {
boolean b = false;
try {
StringBuilder sb = new StringBuilder();
sb.append(request.getHeader("wechatpay-timestamp")+"\n");
sb.append(request.getHeader("wechatpay-nonce")+"\n");
sb.append(JSON.toJSONString(requestBody)+"\n");
Signature signature = Signature.getInstance("SHA256withRSA");
//此处的证书,一定要是微信支付平台的证书,否则验证不会成功。(文档已经提示过,马虎了一下就踩坑)
signature.initVerify(getCertificates());
signature.update(sb.toString().getBytes(StandardCharsets.UTF_8));
String signatureFLg = request.getHeader("wechatpay-signature");
b = signature.verify(Base64Utils.decodeFromString(signatureFLg));
}catch (Exception e){
log.error("校验签名出错",e);
}
return b;
}
/**
* 获取平台证书,证书的获取和处理在文档都有记录
*/
public X509Certificate getCertificates() throws Exception {
//从微信平台获取证书
HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates");
httpGet.setHeader("Accept", "application/json");
//生成签名
httpGet.setHeader("Authorization ", getSign("GET", HttpUrl.parse("https://api.mch.weixin.qq.com/v3/certificates"), ""));
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpGet);
X509Certificate x509Certificate = null;
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
JSONObject certificateVo = JSON.parseObject(EntityUtils.toString(response.getEntity()), JSONObject.class);
List<JSONObject> jsonObjectList = JSON.parseArray(certificateVo.getString("data"), JSONObject.class);
for (JSONObject jsonObject : jsonObjectList) {
//判断证书是否再有效期内
if (DateUtil.compare(new Date(), jsonObject.getTimestamp("effective_time")) > 0
&& DateUtil.compare(new Date(), jsonObject.getTimestamp("expire_time")) < 0) {
JSONObject encrypt_certificate = jsonObject.getJSONObject("encrypt_certificate");
com.wechat.pay.contrib.apache.httpclient.util.AesUtil aesUtil = new AesUtil(WechatPayUtil.apiV3Key.getBytes("utf-8"));
String pulicKey = aesUtil.decryptToString(encrypt_certificate.getString("associated_data").getBytes("utf-8"), encrypt_certificate.getString("nonce").getBytes("utf-8"), encrypt_certificate.getString("ciphertext"));
//获取平台证书
final CertificateFactory cf = CertificateFactory.getInstance("X509");
ByteArrayInputStream inputStream = new ByteArrayInputStream(pulicKey.getBytes(StandardCharsets.UTF_8));
x509Certificate = (X509Certificate) cf.generateCertificate(inputStream);
}
}
} else if (statusCode == 204) { //处理成功,无返回Body
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
response.close();
}
return x509Certificate;
}
3.2 返回参数解析
当通知验签通过后,再进行返回参数解析,解析方式参考 AesUtil 。解析成功的返回值参考支付通知文档 。
//解析返回值,
public String getPayResult(Map<String, Object> body) throws Exception{
//json 为alibaba的fastjson
JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(body), JSONObject.class);
JSONObject resource = jsonObject.getJSONObject("resource");
//WeChatPayAesUtil就是上面的AesUtil
return WeChatPayAesUtil.decryptToString(resource.getString("associated_data").getBytes(), resource.getString("nonce").getBytes(), resource.getString("ciphertext"));
}
4. 其他业务逻辑省略...
- 本文标签: Java
- 本文链接: https://www.tianyajuanke.top/article/67
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权