一、接入准备

微信支付官网:https://pay.weixin.qq.com 注册微信商家号,在账号中心申请API证书及设置APIv3密钥 ,产品中心申请开通Native支付,AppId账号管理关联AppId(微信体系内应用ID) 申请通过后,将得到对接过程中所需要的重要参数:

账号信息内的微信支付商户号mchid微信体系内APPIDAPIv3密钥API证书序列号API证书压缩包

二、创建支付订单

官方Native下单API文档地址 项目内引入 wechatpay-apache-httpclient 依赖(微信支付提供)

com.github.wechatpay-apiv3

wechatpay-apache-httpclient

0.4.4

创建订单测试类

public class CreateOrderTest {

private String privateKey = "MIIEvQ"; // API证书压缩包内apiclient_key.pem文件内容 -> 私钥字符串

private String appId = "wx21121323682f"; // 微信体系 AppId

private String mchId = "1617212105"; // 账号信息 - 微信支付商户号

private String mchSerialNo = "7DCA2B127B9DDD9A87612172B8EB5E8"; // API证书管理 API证书序列号

private String apiV3Key = "n21eddqwdqwde2easjdhas213dfas123"; // APIv3密钥

private CloseableHttpClient httpClient;

@Before

public void nativeRequestTest() throws UnsupportedEncodingException {

// 加载商户私钥(privateKey:私钥字符串)

PrivateKey merchantPrivateKey = PemUtil

.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));

// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)

AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(

new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));

// 初始化httpClient

httpClient = WechatPayHttpClientBuilder.create()

.withMerchant(mchId, mchSerialNo, merchantPrivateKey)

.withValidator(new WechatPay2Validator(verifier)).build();

}

/**

* 微信native下单接口

*/

@Test

public void create() throws Exception{

HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");

// 下单接口金额单位为分

String money = "0.01";

BigDecimal multiply = new BigDecimal(money).multiply(new BigDecimal("100"));

int i = multiply.intValue();

// 商家系统生成订单号 唯一且不可重复,即使关闭微信订单后相同订单号也无法再次创建订单

String out_trade_no = "211014247fb24262a2cc3c8548c24312";

// 请求body参数

// 支付成功回调通知接口 notify_url,必须为https

String reqdata = "{" +

"\"time_expire\": \"2022-10-21T15:38:00+08:00\"," +

"\"amount\": {" +

"\"total\": " + i + "," +

"\"currency\": \"CNY\"" +

"}," +

"\"mchid\": \"" + mchId + "\"," + // 商家号

"\"out_trade_no\": \"" + out_trade_no + "\", " + // 系统订单号

"\"appid\": \"" + appId + "\"," +

"\"description\": \"" + "测试支付" + "\"," +

"\"attach\": \"自定义数据说明\"," +

"\"notify_url\": \"https://143.232.433.42:8285/wxPay/call\" " +

"}";

StringEntity entity = new StringEntity(reqdata, "utf-8");

entity.setContentType("application/json");

httpPost.setEntity(entity);

httpPost.setHeader("Accept", "application/json");

//完成签名并执行请求

CloseableHttpResponse response = httpClient.execute(httpPost);

try {

int statusCode = response.getStatusLine().getStatusCode();

if (statusCode == 200) { //处理成功

System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));

} else if (statusCode == 204) { //处理成功,无返回Body

System.out.println("success");

} else {

System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));

throw new IOException("request failed");

}

} finally {

response.close();

}

}

}

三、关闭订单

/**

* 关闭订单

* 204 订单关闭成功返回状态204 无响应报文

* success

* @throws IOException

*/

@Test

public void close() throws IOException {

HttpPost post = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/211014247fb24262a2cc32118c24312/close");

// 请求body参数

String reqdata = "{" +

"\"mchid\": \"" + mchId + "\"" +

"}";

StringEntity entity = new StringEntity(reqdata, "utf-8");

entity.setContentType("application/json");

post.setEntity(entity);

post.setHeader("Accept", "application/json");

// 完成签名并执行请求

CloseableHttpResponse response = httpClient.execute(post);

int statusCode = response.getStatusLine().getStatusCode();

System.out.println("响应码:" + statusCode);

if (statusCode == 200) { //处理成功

System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));

} else if (statusCode == 204) { //处理成功,无返回Body

System.out.println("success");

} else {

System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));

throw new IOException("request failed");

}

}

四、查询订单信息

/**

* 查询订单

* success,return body = {"amount":{"payer_currency":"CNY","total":1},"appid":"wxe406f9ceabc6682f","mchid":"1617009205","out_trade_no":"211014247fb21231248c24312","promotion_detail":[],"scene_info":{"device_id":""},"trade_state":"NOTPAY","trade_state_desc":"订单未支付"}

* @throws Exception

*/

@Test

public void query()throws Exception{

HttpGet get = new HttpGet("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/211014247fb24262a2cc3c8548c24312?mchid=" + mchId);

get.setHeader("Accept", "application/json");

CloseableHttpResponse response = httpClient.execute(get);

int statusCode = response.getStatusLine().getStatusCode();

if (statusCode == 200) { //处理成功

System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));

} else if (statusCode == 204) { //处理成功,无返回Body

System.out.println("success");

} else {

System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));

throw new IOException("request failed");

}

}

五、支付成功回调通知报文解析

/**

* 微信支付回调响应报文解密工具类

*/

public class AesUtil {

static final int KEY_LENGTH_BYTE = 32;

static final int TAG_LENGTH_BIT = 128;

private final byte[] aesKey;

public AesUtil(byte[] key) {

if (key.length != KEY_LENGTH_BYTE) {

throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");

}

this.aesKey = key;

}

public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)

throws GeneralSecurityException, IOException {

try {

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

SecretKeySpec key = new SecretKeySpec(aesKey, "AES");

GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

cipher.init(Cipher.DECRYPT_MODE, key, spec);

cipher.updateAAD(associatedData);

return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");

} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {

throw new IllegalStateException(e);

} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {

throw new IllegalArgumentException(e);

}

}

}

public class DecodeResponseBodyTest {

private String apiV3Key = "ni3221tezho43she43n434123"; // APIv3密钥

/**

* 微信支付回调报文解密

*

* @throws GeneralSecurityException

* @throws IOException

*/

@Test

public void decode() throws GeneralSecurityException, IOException {

// 回调通知报文

String response = "{\"id\":\"5ae3a07e-0025-5e12-b959-4f7abe76ffbc\",\"create_time\":\"2022-10-21T11:54:50+08:00\",\"resource_type\":\"encrypt-resource\",\"event_type\":\"TRANSACTION.SUCCESS\",\"summary\":\"支付成功\",\"resource\":{\"original_type\":\"transaction\",\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"K/IPHngj23SQvCcmplRuYRt7qjMqHQ9ALG5ou53wG/c331rFf8YjWo+ZDMEgxdRHsmdW4nxrmN7z62h4b98Ka4eCyuxaVaOc1Ddji28cP+u7XFTBWB8Ac9VqVmjQRlv+uM/Z0EIjYYrTx547Bw9soF/sCX9QPtmL9QzvtHlOocHSVDpPgLTrLSR0ruJL+O+PXQ8GQGv/6Jy+bRLmF/5/Lr2CkTybTP2XMfrJevTrKg/OXEHtqtTRdufgW13KdZ7wEHWljZyRM5TqnZOrk04A82+KNXGgaWh5HBtjQ7E+R1wdIiD3+o/aT82M1/FI7eJLRkeiBzIAEkjmFMGkygjZ2+zWpResInCL3VcHIFbl99NKqYDHNAXyUoeARvuI1uwHUBhQyQeF5TQVtNBjiM54K5U5uWTBu2rbsNB5OQU3sFPXzWJTCNKf3UnxMJW5ADpl2AbKzOiBj8bW4fZEy+WR+d2C83PS53R0hXz4g4JEX7Mi5ePP90OIiqI18gmGhfrmaJ9T8tzR8Ik9JSHvaj21coJgf7T4DRKg632Q0rCa5kl8h1saGHthO43jhqymVBDGp/kD6PqU1jvWanuVzIWYKSRCwnrqbgGXPmaupWX8dwphc1sTVA==\",\"associated_data\":\"transaction\",\"nonce\":\"AZ28LA0bMlB5\"}}";

JSONObject jsonObject = JSONObject.parseObject(response);

JSONObject resource = jsonObject.getJSONObject("resource");

// 直接使用 resource.getBytes("nonce") 是错误的

String associated_data = resource.getString("associated_data");

String nonce = resource.getString("nonce");

AesUtil aesUtil = new AesUtil(apiV3Key.getBytes());

String decrypt = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), resource.getString("ciphertext"));

System.out.println("解密后得内容:" + decrypt);

}

}

六、通知签名验证

加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户通过验证签名,以确认请求来自微信,而不是其他的第三方。

/**

* 获取请求头认证签名信息工具类,用于获取商户微信平台证书列表

*/

public class AuthorizationToken {

private String mchId = "1617009205"; // 账号信息 - 微信支付商户号

private String mchSerialNo = "7DCA2B127B9DDD9A876153F3255F3F472B8EB5E8"; // 商户API证书序列号

private String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDfacUE1AeWa4TT" +

"ukE/bsjulIBn7VMvsd+8ASZfUqWs5P4MW0Bqp/U9tb1iIO8ECGnK8csC80/peQe7" +

"SSjdaBFLTWhCOn/4QSjY5wVMNxL9YW17rO2E1YHg+uYpB6i950H27vTh18EOtyeG" +

"g2tgwGTr3/IuPeY5Cy/ZX4VEWnondYGhKWjJYXYtV1ypeBCkLe/A9zoyiIQUUQiz" +

"YYibiU/Ow190Iywzc4oKT1K4lXGdai9OZHzkQorOBkCbx2/9vyLvvUjyfa6RtMWv" +

"qpZuKqjEecQYtqJ6VvJ1N+uKMGDpyChSqBHxG0LT0K5Wu9Gtg5Y+NrYbzMThZUt5" +

"/aJAF0L7AgMBAAECggEARz2RB6Mc8EhEyMchuzp2dC2CbKFu30yXDXpIZCkUj3dN" +

"017dwaThPNZRF5Ns5BpSsdY8aCpyFv7zCjOgBkoDCcIbNtM0r1MH1XKFa/I76fRB" +

"VyijbLIwgi8/aWH52uR9UmKMT9/evfSFdA1AFlADXnvA3CH84b/BeE1PT6aSQTZM" +

"nZ8G7sgcQe8oBz+7oxhJl+wdDscerPg9YHRid/G3L/sjhKjOaMsBJar7gOhYNsop" +

"pfbv51gl8v3iT6luwEkf5zmsM5GfXcyKYKZgKjlI3wOWe2QsvcaprsVQhoMETV1/" +

"I5/KNWIr8sNur7EcEz0TBjLjqNh1pq1M+wTnIi25kQKBgQDzpdt0q5+mdht+ehbm" +

"2X0TWLm89zPOlUZmq+X0UqD5rYTmZ8JHZuaicVaRqUQoTV18IgiHn7lyPyGFYO20" +

"0ROl3aVLrpIqTXzmOYUM1AMtEDr6Gmky8RxPaUskMeOe3liPeF/T+AbYw5M9QRPo" +

"CUTOkldu+w3vKauNPPM6puAmZwKBgQDqvUysPFhAu8YGCqHuJhOcoZkhqh4UrW7o" +

"MEcv4UVAOwnaY55srkBLR5TN8rpaWcvFCD02Vg1nge9fdWiV1wtH81Oz5bIA+nM1" +

"qYnOSu2Z1dqEU/iEaR3t+XCSkvuaqydQ6DYW9GVFt/Mj3xjV9r864QnDMuxGspGk" +

"EY3990jaTQKBgQCjwRJpLLwldfXuoIHp77zXludm8MJaEwv5D4mDF1Hn3U6YSJ5T" +

"vP4/qWskhR4w9CZjur/+30QVXAbcjRPWVjsdXIWvAwpr8h6C4Z/hylDEJcdttviD" +

"a3e6i6scDYfNi+T7sEy/u1BmubOpFKcbabdcGxE2nvdziY8qYw+amPPH+wKBgCfU" +

"Vt4inxbcxYzg4Pj3nPxGryT3KIN5qgfbqTiGkKmFWvajUI5AQsiDLMyFEvmhouGb" +

"tEcz8rJNacBYu5YxFsjukJVFtB5WYJYKXkeSjx47Gwi49sIA1AM8/8zfA7IKuHER" +

"9ZuPfF+IBslfYWdspqXm6TElwtF8GxoroFwnSUVBAoGAS+v+LYcrp4zXMuDk9o9h" +

"3YhzDtedsmTEgR3CHs4hgltdX/jobDfNUk7QVe2ild1O3ULL83r09lf0GoBGxcxt" +

"l5aGMEsdYLQPROanUZqGihBcWqW3P2Sde4rTgFvi+X14JwFbcmn21ddY9bQbbAzv" +

"scKClgUPJMk5d0ZeAeJ/cR8="; // 商户API证书私钥字符串

public String getToken(String method, String url, String body) throws Exception {

String nonceStr = UUID.randomUUID().toString().replace("-","");

long timestamp = System.currentTimeMillis() / 1000;

String message = buildMessage(method, url, timestamp, nonceStr, body);

String signature = sign(message.getBytes("utf-8"));

return "mchid=\"" + mchId + "\","

+ "nonce_str=\"" + nonceStr + "\","

+ "timestamp=\"" + timestamp + "\","

+ "serial_no=\"" + mchSerialNo + "\","

+ "signature=\"" + signature + "\"";

}

String sign(byte[] message) throws Exception {

Signature sign = Signature.getInstance("SHA256withRSA");

// 加载商户私钥(privateKey:私钥字符串)

PrivateKey merchantPrivateKey = PemUtil

.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));

sign.initSign(merchantPrivateKey);

sign.update(message);

return Base64.getEncoder().encodeToString(sign.sign());

}

String buildMessage(String method, String url, long timestamp, String nonceStr, String body) {

return method + "\n"

+ url + "\n"

+ timestamp + "\n"

+ nonceStr + "\n"

+ body + "\n";

}

}

/**

* 验证回调通知签名测试类

*/

public class VerifySignatureTest {

private String apiV3Key = "nin3232312123123432432anyan123"; // APIv3密钥

@Test

public void verify() throws Exception {

// 支付成功回调参数

String time = "1664344490";

String nonce = "fNPfxxlVyJp21232fV93ri1aW";

String serial = "745AB64ADA0A21221CBEAE7B23DB4C08C4CE";

String signature = "k/+nNrE6yRLTzMfriBYA6L1MQc12is/qOmdIKogkIy/FrdavmRB2X4F77gBuhlEG8RM156nqxw+THAEEimuu0nuErUe3GeKRkdU2xG6/aiulQ9dXx8YRr14z15c/vEjEhuN/YhhK9RmRoBACrVcv9+5OwULI1872/CEdhj8DC2WwiRC4nldo+O3jPwS3ma1qkkF6hBYQmXYFbRP5zQRSDsk6FLUq5jq++oHk4kYjIlHR7o1FpvMj1y2tHzOQTOy8pFta3RVoS5iJ5oxzA+9wSwXsMsa1rBOn6xD3TzmqFnoiOd/1bvGbqkFXMIav01AUlrYHzdxokEqS484HWgbeIw==";

String body = "{\"id\":\"5ae3a07e-0025-5e12-b959-4f7abe76ffbc\",\"create_time\":\"2022-10-21T11:54:50+08:00\",\"resource_type\":\"encrypt-resource\",\"event_type\":\"TRANSACTION.SUCCESS\",\"summary\":\"支付成功\",\"resource\":{\"original_type\":\"transaction\",\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"K/IPHngj23SQvCcmplRuYRt7qjMqHQ9ALG5ou53wG/c331rFf8YjWo+ZDMEgxdRHsmdW4nxrmN7z62h4b98Ka4eCyuxaVaOc1Ddji28cP+u7XFTBWB8Ac9VqVmjQRlv+uM/Z0EIjYYrTx547Bw9soF/sCX9QPtmL9QzvtHlOocHSVDpPgLTrLSR0ruJL+O+PXQ8GQGv/6Jy+bRLmF/5/Lr2CkTybTP2XMfrJevTrKg/OXEHtqtTRdufgW13KdZ7wEHWljZyRM5TqnZOrk04A82+KNXGgaWh5HBtjQ7E+R1wdIiD3+o/aT82M1/FI7eJLRkeiBzIAEkjmFMGkygjZ2+zWpResInCL3VcHIFbl99NKqYDHNAXyUoeARvuI1uwHUBhQyQeF5TQVtNBjiM54K5U5uWTBu2rbsNB5OQU3sFPXzWJTCNKf3UnxMJW5ADpl2AbKzOiBj8bW4fZEy+WR+d2C83PS53R0hXz4g4JEX7Mi5ePP90OIiqI18gmGhfrmaJ9T8tzR8Ik9JSHvaj21coJgf7T4DRKg632Q0rCa5kl8h1saGHthO43jhqymVBDGp/kD6PqU1jvWanuVzIWYKSRCwnrqbgGXPmaupWX8dwphc1sTVA==\",\"associated_data\":\"transaction\",\"nonce\":\"AZ28LA0bMlB5\"}}";

// 1.生成签名认证信息 Authorization

String token = getAuthorizationToken();

// 2.根据签名获取平台证书

String certificates = getCertificatesList(token);

// 3.选择正确的一个证书字符串

String certificateJson = chooseCertificate(certificates, serial);

// 4.从报文中解析出证书字符串

String certificate = resolveCertificate(certificateJson);

// 5.将证书字符串转为证书对象

Certificate certificateObj = convertCertificate(certificate);

// 6.拼接验签名串,字符串尾部一定也需要添加\n

String str = Arrays.asList(time, nonce, body).stream().collect(Collectors.joining("\n"))+"\n";

System.out.println("验签字符串:" + str);

// 7.将返回的应答签名进行Base64解码

byte[] signatureByte = Base64.getDecoder().decode(signature);

// 8.验证签名

boolean verifyResult= verifySignature(signatureByte, str, certificateObj);

System.out.println("验签结果:" + verifyResult);

}

/**

* 从报文中选择一个验签的证书

* @param certificates

* @param Serial

* @return

*/

public String chooseCertificate(String certificates,String Serial){

JSONObject jsonObject = JSONObject.parseObject(certificates);

JSONArray data = jsonObject.getJSONArray("data");

for (Object certificate : data) {

jsonObject = (JSONObject) certificate;

if (Serial.equals(jsonObject.getString("serial_no"))){

return JSONObject.toJSONString(jsonObject);

}

}

return null;

}

/**

* 验证签名

* @param signature 微信返回的签名

* @param signStr 验签字符串

* @param certificate 微信平台证书对象

* @return

* @throws Exception

*/

public boolean verifySignature(byte[] signature, String signStr,Certificate certificate) throws Exception {

// 加载SHA256withRSA签名器

Signature sign = Signature.getInstance("SHA256withRSA");

// 用微信平台证书公钥对签名器进行初始化

sign.initVerify(certificate);

// 把我们构造的验签名串更新到签名器中

sign.update(signStr.getBytes(StandardCharsets.UTF_8));

boolean verify = sign.verify(signature);

return verify;

}

/**

* 证书字符串转换为证书对象

* @param certificates

* @return

* @throws CertificateException

*/

public Certificate convertCertificate(String certificates) throws CertificateException {

//获取平台证书

CertificateFactory cf = CertificateFactory.getInstance("X509");

ByteArrayInputStream inputStream = new ByteArrayInputStream(certificates.getBytes(StandardCharsets.UTF_8));

X509Certificate x509Certificate = (X509Certificate) cf.generateCertificate(inputStream);

return x509Certificate;

}

/**

* 解析证书Json字符串得到平台证书字符串

* @param certificateJson

* @return

* @throws Exception

*/

public String resolveCertificate(String certificateJson) throws Exception {

JSONObject jsonObject = JSONObject.parseObject(certificateJson);

JSONObject resource = jsonObject.getJSONObject("encrypt_certificate");

String associated_data = resource.getString("associated_data");

String nonce = resource.getString("nonce");

AesUtil aesUtil = new AesUtil(apiV3Key.getBytes());

String decrypt = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), resource.getString("ciphertext"));

return decrypt;

}

public String getCertificatesList(String token) throws Exception {

String schema = "WECHATPAY2-SHA256-RSA2048";

HttpGet get = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates");

get.setHeader("Authorization", schema + " " + token);

get.setHeader("Accept", "application/json");

get.setHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");

CloseableHttpClient client = HttpClients.createDefault();

CloseableHttpResponse response = client.execute(get);

int statusCode = response.getStatusLine().getStatusCode();

String res = null;

if (statusCode == 200) { //处理成功

res = EntityUtils.toString(response.getEntity());

System.out.println("success,return body = " + res);

} else if (statusCode == 204) { //处理成功,无返回Body

System.out.println("success");

} else {

System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));

throw new IOException("request failed");

}

return res;

}

/**

* 获取签名 用于获取平台证书

*

* @throws Exception

*/

public String getAuthorizationToken() throws Exception {

AuthorizationToken token = new AuthorizationToken();

String t = token.getToken("GET", "/v3/certificates", "");

return t;

}

}

以上代码中关键性身份信息均为错误数据,请大家自行登录微信支付平台申请获取。