微信native掃碼支付遇到的問題(2)

微信支付升級V3後,網上並沒有太多實現V3通知驗籤的代碼邏輯,有的給出的案例非常粗糙,無法直接使用。主要問題有:
1、要用平臺證書籤名,不是配置微信是生成的商戶個人的證書文件或密鑰。
2、平臺證書籤名要通過接口自己獲取,可以解析一次自己保存成文本文件也每次自己讀取,或者將獲取的平臺證書字符串配置成一個變量,這樣做的帶來的問題是證書有有效期,過期失效後微信支付回調肯定會崩了。不建議這樣做。。還是每次獲取或更新比較合理。
3、如何驗籤,如何解密文件,不驗籤也可以解密文件拿到order_trade_noj進行訂單狀態處理呢。 4、v3版本回調通知返回的是json格式的數據,不是xml格式的了。。用v2dexmlToMap方式完全不能用樂,網上搜出來的大多還是這樣的代碼的。。

一、要用平臺證書籤名,不是配置微信是生成的商戶個人的證書文件或密鑰。

第一個問題微信官方也重點強調了多次。

這簡單說就是,你可以對微信回調通知不驗籤,也可以用自己的私鑰解密得到訂單號等相關信息。就是不能保證安全了。爲了安全可靠還是要驗籤。

二、關於平臺證書和驗籤,解密回調
證書獲取直接看官方文檔裏開發指引,如下圖

項目下載下來大概是這樣的:


比較煩人的是項目是用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類裏都有,需要自己稍微改造下

驗籤,解密和後續業務邏輯處理的主代碼都有了,可以根據自己的實際需求改造!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章