微信支付JAVA V3APP下单整合
1. 背景介绍
v3版微信支付通过商户证书和平台证书加强了安全性,java版sdk包wechatpay-apache-httpclient内部封装了安全性相关的签名、验签、加密和解密工作,降低了开发难度。下面几个特性的实现,更方便了开发者。
- 平台证书自动更新,无需开发者关注平台证书有效性,无需手动下载更新;
- 执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。
如果文档中有错误的地方,需要路过的大佬指出,我会尽快更改。跪谢。。。
2. API证书
2.1 API 密钥设置( 一定要设置的!!!! )
请登录商户平台进入【账户中心】->【账户设置】->【API安全】->【APIv3密钥】中设置 API 密钥。
具体操作步骤请参见:什么是APIv3密钥?如何设置?
2.2 获取 API 证书
请登录商户平台进入【账户中心】->【账户设置】->【API安全】根据提示指引下载证书。
具体操作步骤请参见:什么是API证书?如何获取API证书?
简单叙述:(详细信息请看官方文档)
证书 | 描述 | 备注 |
---|---|---|
apiclient_cert.p12 | 包含了私钥信息的证书文件 | 是商户证书文件(双向证书) |
apiclient_cert.pem | 从apiclient_cert.p12中导出证书部分的文件,为pem格式 | 简单理解:公钥 |
apiclient_key.pem | 从apiclient_key.pem中导出密钥部分的文件 | 简单理解:私钥 |
3. 创建client(请参照官方文档)
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
@Slf4j
@Aspect
@Component
public class WxPayHttpClientFactory {
public static CloseableHttpClient httpClient;
public static Verifier verifier;
@Before("execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))" +
"||execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))")
public void initWXPayClient() {
try {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ClassPathResource("apiclient_key.pem classpath路径").getInputStream());
X509Certificate certificate = PemUtil.loadCertificate(new ClassPathResource("apiclient_cert.pem classpath路径").getInputStream());
String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
//merchantId:商户号,serialNo:商户证书序列号
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant("商户号", new WechatPay2Credentials("商户号",
new PrivateKeySigner(serialNo, merchantPrivateKey)), wechatAppPayConfig.api_v3.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
//版本>=0.4.0可使用 CertificatesManager.getVerifier(mchId) 得到的验签器替代默认的验签器。
// 它会定时下载和更新商户对应的微信支付平台证书 (默认下载间隔为UPDATE_INTERVAL_MINUTE)。
verifier = certificatesManager.getVerifier("商户号");
//创建一个httpClient
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant("商户号", serialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
} catch (IOException e) {
e.printStackTrace();
log.error("加载秘钥文件失败");
} catch (GeneralSecurityException e) {
e.printStackTrace();
log.error("获取平台证书失败");
} catch (Exception e) {
e.printStackTrace();
}
}
@After("execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))" +
"||execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))")
public void closeWXClient() {
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
官方文档指出 自动携带身份认证信息,并检查应答的微信支付签名 。所以上面的代码就没携带任何签名。
4. 生成APP支付订单(请参照官方文档)
/**
* 微信支付POST请求
*
* @param reqUrl 请求地址 示例:https://api.mch.weixin.qq.com/v3/pay/transactions/app
* @param paramJsonStr 请求体 json字符串 此参数与微信官方文档一致
* @return 订单支付的参数
* @throws Exception
*/
public static String V3PayPost(String reqUrl, String paramJsonStr) throws Exception {
//创建post方式请求对象
HttpPost httpPost = new HttpPost(reqUrl);
//装填参数
StringEntity s = new StringEntity(paramJsonStr, "utf-8");
s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
"application/json"));
//设置参数到请求对象中
httpPost.setEntity(s);
//指定报文头【Content-type】、【User-Agent】
httpPost.setHeader("Content-type", "application/json");
httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
httpPost.setHeader("Accept", "application/json");
//执行请求操作,并拿到结果(同步阻塞)
CloseableHttpResponse response = WxPayHttpClientFactory.httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
//获取数据,并释放资源
String body = closeHttpResponse(response);
if (statusCode == 200) { //处理成功
switch (reqUrl) {
case "https://api.mch.weixin.qq.com/v3/pay/transactions/app"://返回APP支付所需的参数
return JSONObject.parseObject(body).getString("prepay_id");
case "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"://返回APP退款结果
return body;
}
}
return null;
}
/**
* 获取数据,并释放资源
*
* @param response
* @return
* @throws IOException
*/
public static String closeHttpResponse(CloseableHttpResponse response) throws IOException {
String body = "";
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
//获取结果实体
HttpEntity entity = response.getEntity();
if (entity != null) {
//按指定编码转换结果实体为String类型
body = EntityUtils.toString(entity, "utf-8");
}
//EntityUtils.consume将会释放所有由httpEntity所持有的资源
EntityUtils.consume(entity);
}
//释放链接
response.close();
return body;
}
至此就成功 在微信支付服务后台生成预支付交易单,并且拿到prepay_id
,但是调起APP支付,还需其他字段,也就是所谓的二签。可以直接使用下面的方法WxAppPayTuneUp()
5.APP调起支付(请参照官方文档)
/**
* 微信调起支付参数
* 返回参数如有不理解 请访问微信官方文档
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
*
* @param prepayId 微信下单返回的prepay_id
* @param appId 应用ID
* @param mch_id 商户号
* @param private_key_path 私钥路径
* @return 当前调起支付所需的参数
* @throws Exception
*/
public static String WxAppPayTuneUp(String prepayId, String appId, String mch_id, String private_key_path) throws Exception {
if (StringUtils.isNotBlank(prepayId)) {
long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = generateNonceStr();
//加载签名
String packageSign = sign(buildMessage(appId, timestamp, nonceStr, prepayId).getBytes(), private_key_path);
JSONObject jsonObject = new JSONObject();
jsonObject.put("appId", appId);
jsonObject.put("prepayId", prepayId);
jsonObject.put("timeStamp", timestamp);
jsonObject.put("nonceStr", nonceStr);
jsonObject.put("package", "Sign=WXPay");
jsonObject.put("signType", "RSA");
jsonObject.put("sign", packageSign);
jsonObject.put("partnerId", mch_id);
return jsonObject.toJSONString();
}
return "";
}
public static String sign(byte[] message, String private_key_path) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
//签名方式
Signature sign = Signature.getInstance("SHA256withRSA");
//私钥
sign.initSign(PemUtil
.loadPrivateKey(new ClassPathResource(private_key_path).getInputStream()));
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 按照前端签名文档规范进行排序,\n是换行
*
* @param appId appId
* @param timestamp 时间
* @param nonceStr 随机字符串
* @param prepay_id prepay_id
* @return
*/
public static String buildMessage(String appId, long timestamp, String nonceStr, String prepay_id) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ prepay_id + "\n";
}
protected static final SecureRandom RANDOM = new SecureRandom();
//生成随机字符串 微信底层的方法,直接copy出来了
protected static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(RANDOM.nextInt("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".length()));
}
return new String(nonceChars);
}
将WxAppPayTuneUp()返回的字符串,响应给前端即可
6.支付通知(异步通知,请参照官方文档)
@PostMapping("/wxAppPayNotify")
public JSONObject wxAppPayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException, GeneralSecurityException {
//从请求头获取验签字段
String signature = request.getHeader("Wechatpay-Signature");
String serial = request.getHeader("Wechatpay-Serial");
ServletInputStream inputStream = request.getInputStream();
StringBuilder sb = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
//读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
sb.append(s);
}
String s1 = sb.toString();
//按照文档要求拼接验签串
String verifySignature = request.getHeader("Wechatpay-Timestamp") + "\n"
+ request.getHeader("Wechatpay-Nonce") + "\n" + s1 + "\n";
//使用官方验签工具进行验签
boolean verify1 = WxPayHttpClientFactory.verifier.verify(serial, verifySignature.getBytes(), signature);
//判断验签的结果
if (!verify1) {
//验签失败,应答接口
//设置状态码
response.setStatus(500);
JSONObject jsonResponse = new JSONObject();
jsonResponse.put("code", "FAIL");
jsonResponse.put("message", "失败");
return jsonResponse;
}
JSONObject parseObject = JSONObject.parseObject(s1);
if ("TRANSACTION.SUCCESS".equals(parseObject.getString("event_type"))
&& "encrypt-resource".equals(parseObject.getString("resource_type"))) {
//通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
//通知的资源数据类型,支付成功通知为encrypt-resource
JSONObject resourceJson = JSONObject.parseObject(parseObject.getString("resource"));
String associated_data = resourceJson.getString("associated_data");
String nonce = resourceJson.getString("nonce");
String ciphertext = resourceJson.getString("ciphertext");
//解密,如果这里报错,就一定是APIv3密钥错误
AesUtil aesUtil = new AesUtil(api_v3.getBytes());
String aes = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
System.out.println("解密后=" + aes);
//dosomething 处理业务
}
}
JSONObject jsonResponse = new JSONObject();
jsonResponse.put("code", "SUCCESS");
jsonResponse.put("message", "成功");
//设置状态码
response.setStatus(200);
return jsonResponse;
}
以上是一套APP下单的流程【APP下单】->【APP调起支付】->【微信支付异步通知】
作者:cchilei
-------------------------------------------
个性签名:竹杖芒鞋轻胜马 一蓑烟雨任平生
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!