微信支付升级V3后,网上并没有太多实现V3通知验签的代码逻辑,有的给出的案例非常粗糙,无法直接使用。主要问题有:
1、要用平台证书签名,不是配置微信而是生成的商户个人的证书文件或密钥。
2、平台证书签名要通过接口自己获取,也可以解析一次自己保存成文本文件每次自己读取,或者将获取的平台证书字符串配置成一个变量,这样做带来的问题是证书是有有效期的,过期失效后微信支付回调肯定会崩了。不建议这样做。。还是每次获取或更新比较合理。
3、如何验签?如何解密文件,不验签其实也可以拿到order_trade_noj进行订单状态处理,但保证不了发起方是微信,若第三方伪造发起回调会导致订单状态改变,所以有必要进行验签以保证是微信发起的。
4、v3版本回调通知返回的是json格式的数据,不是xml格式的了。。用v2版本的方法xmlToMap完全不能用了,网上搜出来的大多还是这样的代码的。
一、要用平台证书签名,不是配置微信是生成的商户个人的证书文件或密钥。
第一个问题微信官方也重点强调了多次。
简单说,你可以对微信回调通知不验签,也可以用自己的私钥解密得到订单号等相关信息。就是不能保证安全了。为了安全可靠还是要验签。
二、关于平台证书和验签,解密回调
证书获取直接看官方文档里开发指引,如下图
项目下载下来大概是这样的:
比较烦人的是项目是用gradle构建的。。没有pom。。干脆直接把测试类挪到我们的maven项目。。
用到的依赖
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.2</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-Core</artifactId>
<version>2.7.4</version>
</dependency>
改装后完整demo代码如下:
package test.wechat.pay;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.RsaKit;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
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.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
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.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @Desc 微信扫码支付
* @Author lisha 2021/7/15 0015 9:27
*/
@Slf4j
public class WXQrcodePayUtilTest {
private static String mchId = ""; // 商户号
private static String mchSerialNo = ""; // 商户证书序列号
private static String apiV3Key = ""; // apiV3密钥
// 你的商户私钥
private static String privateKey = "-----BEGIN PRIVATE KEY-----\n"
+ "-----END PRIVATE KEY-----\n";
// 你的平台证书 - 返回的平台证书-可以获取一次后定义成变量,但有效期过后需要手动获取再更新,否则回调验签失败
private static String CERTIFICATE_KEY = "-----BEGIN CERTIFICATE-----\n" +
"-----END CERTIFICATE-----";
//测试AutoUpdateCertificatesVerifier的verify方法参数
private static String serialNumber = "";
private static String message = "";
private static String signature = "";
private CloseableHttpClient httpClient;
private AutoUpdateCertificatesVerifier verifier;
/**
* 使用前先初始化方法
*
* @param mchId 商家编号
* @param mchSerialNo 商家序列号
* @param apiV3Key api密钥 32位字符串
* @throws IOException
*/
// @Before
public void setup(String mchId, String mchSerialNo, String apiV3Key) throws IOException {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new ByteArrayInputStream(privateKey.getBytes("utf-8")));
//使用自动更新的签名验证器,不需要传入证书
verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
apiV3Key.getBytes("utf-8"));
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.build();
}
// @After
public void after() throws IOException {
httpClient.close();
}
// @Test
public void autoUpdateVerifierTest() throws Exception {
assertTrue(verifier.verify(serialNumber, message.getBytes("utf-8"), signature));
}
/**
* 通过接口自动获取证书-并解析,这里把证书序列号和平台证书字符串放进map里,可能有多个证书。。取回调通知对应序列号里的。
* @param apiV3Key
* @return
* @throws Exception
*/
public Map<String, String> getCertificateMap(String apiV3Key) throws Exception {
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/certificates");
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response1 = httpClient.execute(httpGet);
assertEquals(200, response1.getStatusLine().getStatusCode());
Map<String, String> certificateMap = new LinkedHashMap<>(2);
try {
// 存放证书序列号和平台公钥
HttpEntity entity1 = response1.getEntity();
EntityUtils.consume(entity1);
log.info("content={}", entity1.getContent());
InputStream inStream;
JSONObject json = new JSONObject();
inStream = (InputStream) response1.getEntity().getContent();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), Charset.defaultCharset());
log.info("获取证书返回具体内容result=" + result);
json = JSONObject.parseObject(result);
/** 获取证书返回参数示例
* {
* "data": [{
* "effective_time": "2019-11-29T11:14:26+08:00",
* "encrypt_certificate": {
* "algorithm": "AEAD_AES_256_GCM",
* "associated_data": "certificate",
* "ciphertext": "NgF+gQS24db69XtDBAcqB+DE4H1LwvtISMAM82BYTitmM2QzhzUIlbZT8rh3MRIgjnlxDY0za9xLAyKq6O91aUrL4E7lR0cH64eZYnp7iE/oc9ZQTdltqH82mSDMa2F9bWagaW0UWtdt0G2zMWc6eAviu+Luu3EG9uPlQk0lRTPmBSpMs9jObUZvUy9/8m+Dul5pjZ6+aqmL/LihgXXcUaeo4OLzslnQgJcqHUJM13TAZQDZWt1fiOPud8SbpLmaBRTon7peOR/J9qCzdeGPrZ2gi7pTzxHNl3gFDwACqzN0Z0rw3iPHPi0Mib3/rLXWiBg4TaIJocRWjjoiT/PDq4hcBV9AQ3KEcWsI3Zbm/9lYuNdcWlEZQXC2YITTqPtm3QALOF5MkXks8EhXqD0HGG1073hpZAOxaA3uLjhjs/tcgAsJs3qXpCtSYFmRh2iT1NmUD7yN8L1nKTmnGYTWgGPgIFgggryuSHS76HZA4/5HOlmTKp9DxCQb+H+0beuFbln+hiG4WKc/mKzpSpl7TR6vw2EjSa7Fwx9mdqLLVrr52sgUt2fvd0S+a5KwiEQIiaKKthao2WQx4v6omAqgk8hNcJ3H22aCJNdbjSc9QHWrWwe7P7rcW5SdwLI5rP1o4M477EV0WScSxT+3d09SrrSXDv5y+aWTX4p+cniN0e4vfPHxVBuUGQmR7s8nVCz2TzvKp4nzAiGVvKIJ2nCryyZHyU8q0UicT3H7bcmZdctdzmcGbSzk8M8oKzgHBWJVfUOvOmCeGOJNYMQ3fPxNSMsRIomyTHKAGsn9kUelAULeRLv2Sqn0livrFJNGwFG1l+iAvBLbAvn30mFZ3ytjkeObeGF4VwnW29QsZk35FEj83wfTIPBSOcAJina7cXjRZBrS6mUzB1641Mr+QWzmoeJ8w9QOvbEn4lwGiX4W5Eadau0xvht0GWHA130/Mld40qLwVAnj7MOTpyKooriB+eULQp01NYirAxpSIUlgUZWUdvA+aOJyE59E9XvQ0iV29CuaVK1qhGWQnpg6WEPNNQhwtbb4BznXVKG0pdQZDFG4Qzu//0QaV2AEbk0/hDRiO4zpOMiODH5R2ns0VrS81T9D1aCeXkqjk62SHnKxOYeQZoGDUj1JMpMLXQ9yX87A0vpzTcBQEO0O+UcNVVjU6LaHmoxGnyjj4eJHjpCHoM0r91GR++FheIlCYfmH6TkVyQSjpd5Ho2JRdCr+HaC+ykN9BenCB22+BoBhzJN9Ce2+ynRqty4mXalV1/NKpd2I9ojOYgckf9nVpGbzCTp/HmR5qfyx1wfd0L6j6UMdjeXVOslKn8D1Ku5ANDeE3TePMYQvJGiJbhv8wGEHCnSfPT8DVj9YxRelXpo11iEx8XT4WkV5BYXAJaCB17otPMpq+o7HJDEcnuT1URh02rmTsZzunfqnCvTMdyaLsXC0nWRoR40H1R1ZXfwPhmrXn7aI0UAJAktdBb56P8lfPM1kpkpFsyYC78NHivdF1Wyoxx+D1U5Dv21GjAI0onUcfZ18aTxPPpjkdGTZewPqHYNhw6DkVby3rWU70D9HPTOzNKxr2y9vEJnJ6IhInAn2Av2xkH05dgdIeRBHrOTk2I7tj03R7HwulhGfryfHoLEgHkZTr/wxihVN0AqaXerOLYam0U2D4pFFLDR8gJ7bourVZmOTNx2Xr1Y50Qh2Sh/jqnOy+GO3TBaxO/qWrmIqhzCISubTm5dSAJ9MTwFss1J/gagUNKEiw86Vo2NlYkJc/9F2drJ/EbOaMKiC0GKqb4eOESUpNPisG314o1pIgE0pJSOR7vQCsoDG8OdW4DqcalTYe0CfnKC4a1Fs+pvs3zwOtfCghqMLTonRXuo6QVr/OBK9A2k9Bg==",
* "nonce": "18ffc3954018"
* },
* "expire_time": "2024-11-27T11:14:26+08:00",
* "serial_no": "4065F385749EDF4997432F091F0B0D04CC54E775"* }]
* }
*/
// 证书可能是数组
JSONArray data = json.getJSONArray("data");
for (Object item : data) {
JSONObject jsonObject = JSONObject.parseObject(item.toString());
// 证书序列号
String serial_no = jsonObject.getString("serial_no");
JSONObject certJson = jsonObject.getJSONObject("encrypt_certificate");
String associated_data = certJson.getString("associated_data");
String ciphertext = certJson.getString("ciphertext");
String algorithm = certJson.getString("algorithm");
String nonce = certJson.getString("nonce");
String certKey = decryptResponseBody(associated_data, nonce, ciphertext, apiV3Key);
log.info("serial_no={}----certString={}", serial_no, certKey);
certificateMap.put(serial_no, certKey);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
response1.close();
}
return certificateMap;
}
/**
* 解析二维码回调返回的字符串-解密主方法
* @param associatedData
* @param nonce
* @param ciphertext
* @param apiV3Key
* @return
*/
public static String decryptResponseBody(String associatedData, String nonce, String ciphertext, String apiV3Key) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
byte[] bytes;
try {
bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException(e);
}
String result = new String(bytes, StandardCharsets.UTF_8);
System.out.println("decryptResponseBody 解密扫码支付数据结果为 result=" + result);
return StringUtils.isEmpty(result) ? null : result;
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 获取证书测试方法
*/
@Test
public void test01(){
try {
this.setup(mchId, mchSerialNo, apiV3Key);
Map<String, String> certificateMap = getCertificateMap(apiV3Key);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 微信扫码支付验签
* @param signature
* @param timestamp
* @param nonce
* @param body
* @param serialNo
* @param apiV3Key
* @return
*/
public boolean checkQrcodeSign(String signature, String timestamp, String nonce, String body, String serialNo, String apiV3Key){
// String signature = "xW6fzlfjnbC3c8aPi3u46ZNF7LEI+dnkPDcYdZVtz0jabxB5RGi/6B2pxKteNIXUVU9hSmKd2v1EZeFKi8PHOqHZYncdgpgSQQ5gWgMOqOsTicTtR5tfxv1Db5A0AX8MhuaR/y9pOGVAUyoFIfmYK8bXy+B47fJThxcGkJ3IxiowITyY5WDyZSukmQ9oOWSnUu+uhtEUwnJVy92OuQav8YRh8YzlKAR3b8QOlabjcqKF07qp7q47yTtUBWdKR0jLY14VVQUEx/62aVzQIuOxu7vlSDIHYsK9rwmexCBx/sQJJ3s8R3xAGy7e/vExDlh1YfHweDj/N0j21sqjeXjiNw==\n";
// String timestamp = "1626602637";
// String nonce = "R64fu9fs97SulzMBY31WDN5t04TQcrYX";
// String body = "{\"id\":\"f535a308-5b31-514d-8e2b-5f775eb3b1fe\",\"create_time\":\"2021-07-18T18:03:57+08:00\",\"resource_type\":\"encrypt-resource\",\"event_type\":\"TRANSACTION.SUCCESS\",\"summary\":\"支付成功\",\"resource\":{\"original_type\":\"transaction\",\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"xEYV2sIuQgg1ybJqPf9ZDu49rN67YIiKf7xrId5KKW3/YBExy8pmZQyxilSSrO41M+M8hzs2O7HojzDhf0Ht04VCvNNeGi6xpbukVUzGK1iwpPKbdaPYpsbg+IP3XYZqnvTBr0kAqJD9hG4PfmINtPzwTlbsMf/5RZs8Nv1rQuEJ/aZxxfFmY4EW9Aa10crbfW6Wi9xKzWhO2zavABBWwql73/4f7QZwhTWnXU1YFh/dK0ts90b5ezkY2Fba27fEOCeB3N7zFkA/5PLnzuQdMOMpuIEoalgUNIYlHBIDKmHJoR3Vmjwoxp4e6VK9VoJGFnixJayl9EtaZJdIxRs3JLKSSs3KNapurC9NpgaXnSXbNbWryx6xQE9BMmRks2xd6ypTOpQgb2ScUg9FGjoZF0Mp+bACrijxF0kICCORSNrlGJfXE+Y1TP6/IcHTYTYb60/GlQXu92GjfPvp8nuRVEvvbYjmzk3UKCZ934l6oeyLk1d3KcqnO0dKARercxPswtSCoQ+zchal7khAsrY/TUYRWOcyrDZJX0Fecdj40mzq5n1WlO4GJ/GMVjjTcyQTrw4B3f2dSTxyg1GkHg==\",\"associated_data\":\"transaction\",\"nonce\":\"5qoaxx3iUjuj\"}}";
boolean verifySignature = false;
try {
Map<String, String> certificateMap = getCertificateMap(apiV3Key);
String certificateText = certificateMap.get(serialNo);
ByteArrayInputStream inputStream = new ByteArrayInputStream(certificateText.getBytes("utf-8"));
verifySignature = verifySignature(signature, body, nonce, timestamp, inputStream);
System.out.println("verifySignature=" + verifySignature);
} catch (Exception e) {
e.printStackTrace();
log.error("微信扫码支付验签失败");
}
return verifySignature;
}
/**
* 返回验签结果
* @param signature
* @param body
* @param nonce
* @param timestamp
* @param certInputStream
* @return
* @throws Exception
*/
public boolean verifySignature(String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception {
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);
// 获取证书
X509Certificate certificate = PayKit.getCertificate(certInputStream);
PublicKey publicKey = certificate.getPublicKey();
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
}
/**
* 通过实时获取平台key进行测试,注意用实际业务回调返回的参数放进方法进行校验时返回参数有效期应该在3-5分钟内有效,超时会一直校验失败
*/
@Test
public void test1(){
String mchId = ""; // 商户号
String mchSerialNo = ""; // 商户证书序列号
String apiV3Key = ""; // api密钥
String signature = "lakmcVS9ScyEPygWVZchgYv1Jjysv1wRRRJaScqPI/gyAQMZ7vGjTfJdMUnnMAXIdoyTDCONLJP0Fs65ggS3MCzqMCTVuivTHivQZjRfOljwlq3+tid+RKMt8LsYkJxa9mrxVL6D4xIJrIwdSDQ1MsGeaPZ5EhdbmIUAGs8SRm2M5ds/T1hVQ27x0VAOHRWPLfXv4NWTst7U01YNpuI/ZGTwJQH6Ae51YOZnNBx/gD6UKQUBdNl0gCyhyZ0XwCpoRTg3jdd0BAjN2yAuSledbAa6f65DHi+TK1miSj7F8lmb5PhE7po9rEuEv3yfQIcl3cv35Wb5ivdJ5Idw3/e77A==";
String timestamp = "1626607308";
String body = "{\"id\":\"2b2cbbdd-fe3f-53ec-a1c4-9d2e074b024a\",\"create_time\":\"2021-07-18T19:21:48+08:00\",\"resource_type\":\"encrypt-resource\",\"event_type\":\"TRANSACTION.SUCCESS\",\"summary\":\"支付成功\",\"resource\":{\"original_type\":\"transaction\",\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"Mg9+QVBcNQfy/tcblJCILgX52X1ntb6JBZW6tYeq1Bkd/Ul5CFD5bEJkr+5C71ruKymIUIZliSwAhMxeyru1wob0+gWQIxN8Fuk7I3UFi9dBsPyoQDXT0UeBdocPqnwWbRcbNr5UGQ46Ra2cg4DhwTDqRTV68MySdzn4HZV5qEVnhpL3hr9NWcnI8/bL/wW1QmN0QtiKQZFFPupdAGpqKd4p9r01hPzamAquhOC5NcgFxKEfNtvXLpavQKpwklw2uoUEH5Z1tOUnK4vx/4evZZ5VLwTInvZzOMCxBJ4/YWkwEnyYbUSoNy6YuSvyBb7bPpNmMJGKrsCKGWtQGcytZkO8X2dscVcC7Bg7/xiEpbBSC768OyTf/7pHqJck2dXtIGfHSCkOstT3FtKrBxYvdbt2NNByMDRIVaSHA/EwSEAmAviioetqXFh6mLtf6B0fuOi478Fu7H74dyEJ9e3UEN6n7K+8ReYyhIKQU5gjmADbF8RWqttiP2VZ3Z0bqYjrupf+ZStgiYu9tvSOUbGKU80V52/HW5PEsYSkWuflwxw4RtKX7tdypwL0TpXXRNYKJiDt+8cuRqOtXTldbQ==\",\"associated_data\":\"transaction\",\"nonce\":\"4SOYhSmcwAMl\"}}";
String nonce = "vf2l4dkaOlZYbWs4FNdL5X79ICmYYJBA";
String serial = "4065F385749EDF4997432F091F0B0D04CC54E775";
try {
this.setup(mchId, mchSerialNo, apiV3Key);
boolean flag = this.checkQrcodeSign(signature, timestamp, nonce, body, serial, apiV3Key);
log.info("返回结果为={}", flag);
} catch (IOException e) {
e.printStackTrace();
}
}
}
其中的body参数就是微信回调从request里获取的body数据流转成的字符串 代码都有,可以根据自己的实际业务改造后直接使用。
V3验签、解密过程代码。
微信扫码后回调通知controller方法
微信扫码支付回调地址
@RequestMapping("/qrcodePayCallback")
public void notifyLogic(HttpServletRequest request, HttpServletResponse response){
log.info("WXPayCommonController 微信扫码支付回调业务处理-开始.....");
InputStream inStream;
JSONObject json = new JSONObject();
Map<String, String> responseMap = new HashMap<>(2);
String responseXml = null;
try {
inStream = (InputStream) request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), Charset.defaultCharset());
Boolean signValid = checkSign(request, result);
if(!signValid){
response.setStatus(500);
responseMap.put("return_code", "FAIL");
responseMap.put("return_msg", "ERROR");
return;
}
json = JSONObject.parseObject(result);
log.info("------微信扫码支付返回数据的结果为:" + result);
JSONObject resource = json.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String nonce = resource.getString("nonce");
String associated_data = resource.getString("associated_data");
// 解密后的数据内容 用WXQrcodePayUtilTest.decryptResponseBody()方法解密也可以,注意参数顺序
String decryptResult = WXQrcodePayUtil.decryptResponseBody(associated_data, nonce, ciphertext, wxPayConfig.getApiV3Key());
JSONObject paramMap = JSONObject.parseObject(decryptResult);
boolean flag = this.notifyLogic(paramMap);
log.info("------decryptResult 微信扫码支付返回解密后数据的结果为:" + decryptResult);
if(flag){
log.info("----------扫码支付回调响应本地业务逻辑处理成功--------");
responseMap.put("return_code", "SUCCESS");
responseMap.put("return_msg", "OK");
response.setStatus(200);
try {
responseXml = WXPayUtil.mapToXml(responseMap);
} catch (Exception e) {
e.printStackTrace();
response.setStatus(500);
responseMap.put("return_code", "FAIL");
responseMap.put("return_msg", "ERROR");
log.info("-----------微信扫码支付回调响应返回失败--------");
}
} else {
log.info("----------失败-扫码支付回调响应本地业务逻辑处理失败--------");
responseMap.put("return_code", "FAIL");
responseMap.put("return_msg", "ERROR");
response.setStatus(500);
}
response.setContentType("text/xml");
response.getWriter().write(responseXml);
response.flushBuffer();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 微信扫码校验验签-验签需要在微信回调给本地的request对象header里取出4个参数,来保证回调是从微信发出的
* @param request
* @param body
* @return
*/
public Boolean checkSign(HttpServletRequest request, String body){
boolean flag = false;
try {
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String serial = request.getHeader("Wechatpay-Serial");
log.info("------------------------验签参数开始----------------------------------");
log.info("nonce={}", nonce);
log.info("signature={}", signature);
log.info("timestamp={}", timestamp);
log.info("serial={}", serial);
log.info("body={}", body);
// 返回的header字符串末尾有\n符号,需要都去掉,否则导致验签失败
signature = signature.replace("\n", "");
body = body.replace("\n", "");
serial = serial.replace("\n", "");
nonce = nonce.replace("\n", "");
WXQrcodePayUtil wxUtil = new WXQrcodePayUtil();
wxUtil.setup(wxPayConfig.getMchID(), wxPayConfig.getMchSerialNo(), wxPayConfig.getApiV3Key());
flag = wxUtil.checkQrcodeSign(signature, timestamp, nonce, body, serial, wxPayConfig.getApiV3Key());
log.info("------------------------验签参数结束----------------------------------");
} catch (IOException e) {
e.printStackTrace();
}
return flag;
}
/**
* 回调订单业务逻辑处理-用订单号改状态
* @param map
* @return
*/
public boolean notifyLogic(JSONObject jsonParam) {
log.info("处理微信扫码支付详细业务逻辑");
InputStream inStream;
JSONObject json = new JSONObject();
try {
String out_trade_no = jsonParam.getString("out_trade_no");
String trade_state = jsonParam.getString("trade_state");
String transaction_id = jsonParam.getString("transaction_id");
String cash_fee = jsonParam.getJSONObject("amount").getString("total");
log.info("订单编号和交易状态----out_trade_no={}----trade_state={}---cash_fee={}", out_trade_no, trade_state, cash_fee);
Map<String, String> reqData = new HashMap<>();
reqData.put("transaction_id", transaction_id);
reqData.put("cash_fee", cash_fee);
//3.修改订单状态
if("SUCCESS".equals(trade_state)){
wxPaymentService.updateMemberStateByOrderNo(out_trade_no, reqData);
log.info("notifyLogic - 支付处理成功");
return true;
}else{
//记录日志
log.info("notifyLogic - 支付处理失败");
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
代码里WXQrcodePayUtil类用到的方法WXQrcodePayUtilTest类里都有,需要自己稍微改造下
验签,解密和后续业务逻辑处理的主代码都有了,可以根据自己的实际需求改造!