Understand all knowledge points of "wechat payment Api-v3" interface rules

Article directory

brief introduction

In order to provide merchants with a simple, consistent and easy-to-use development experience on the premise of ensuring payment security, we launched a new wechat payment API v3.

In fact, a major factor is "to meet regulatory requirements.".

It is mainly to meet the regulatory requirements and ensure a higher level of security. According to the electronic signature law of the people's Republic of China, the financial electronic authentication standard and the administrative measures for online payment business of non bank payment institutions, "if the electronic signature needs to be authenticated by a third party, the legally established electronic authentication service provider shall provide authentication services." Therefore, a third-party CA is needed to ensure the uniqueness, integrity and non repudiation of the digital certificate.

The same is true of Alipay payment. The public key certificate mode has been added from the previous "common public key" mode. Today's protagonist is WeChat paid Api v3, here is not to start talking about Alipay paid.

Wechat payment Api v3 interface rules Official documents

The difference between v2 and v3

V3 Rule difference V2
JSON Parameter format XML
POST, GET or DELETE Submission mode POST
AES-256-GCM encryption Callback encryption No encryption required.
RSA encryption Sensitive encryption No encryption required.
UTF-8 Encoding mode UTF-8
Asymmetric key SHA256-RSA Signature method MD5 or HMAC-SHA256

Wechat payment Api-v2 version details please refer to the blog before parameters Wechat payment, everything you want to know is here

More dry goods, less bullshit. Now go to the topic. After reading the full text, you will Get to the following knowledge points

  • How to get certificate serial number
  • Asymmetric key SHA256-RSA encryption and verification signature
  • How to decrypt AES-256-GCM

API key settings

Please log in the merchant platform and enter account center - > account settings - > API security - > APIv3 key to set the API key.

Please refer to: What is an APIv3 key? How to set it up?

Get API certificate

Please log in to the merchant platform and enter [account center] - > account settings - > API security] to download the certificate according to the instructions.

Please refer to: What is an API certificate? How to obtain API certificate?

Follow the above steps to get the following:

  • apiKey API key
  • apiKey3 APIv3 key
  • mchId merchant number
  • apiclient_key.pem X.509 key of standard certificate
  • Certificate + key of apiclient_cert.p12 X.509 standard
  • Certificate of apiclient_cert.pem X.509 standard

Request signature

How to generate signature parameters? Official documents If the description is very clear, there will be no verbosity here.

Sample code

Construct signature string

    /**
     * Construct signature string
     *
     * @param method    {@link RequestMethod} GET,POST,PUT etc.
     * @param url       Request interface / v3/certificates
     * @param timestamp Get the current system timestamp when the request is initiated
     * @param nonceStr  Random string
     * @param body      Request message body
     * @return String to be signed
     */
    public static String buildSignMessage(RequestMethod method, String url, long timestamp, String nonceStr, String body) {
        return new StringBuilder()
                .append(method.toString())
                .append("\n")
                .append(url)
                .append("\n")
                .append(timestamp)
                .append("\n")
                .append(nonceStr)
                .append("\n")
                .append(body)
                .append("\n")
                .toString();
    }

Constructing Authorization in HTTP header

/**
 * Authorization required to build v3 interface
 *
 * @param method    {@link RequestMethod} Request method
 * @param urlSuffix It can be obtained through WxApiType, and the URL mounting parameters need to be spliced by themselves
 * @param mchId     Merchant Id
 * @param serialNo  Merchant API certificate serial number
 * @param keyPath   key.pem Certificate path
 * @param body      Interface request parameters
 * @param nonceStr  Random character library
 * @param timestamp time stamp
 * @param authType  Authentication type
 * @return {@link String} Return Authorization required by v3
 * @throws Exception Abnormal information
 */
public static String buildAuthorization(RequestMethod method, String urlSuffix, String mchId,
                                        String serialNo, String keyPath, String body, String nonceStr,
                                        long timestamp, String authType) throws Exception {
    // Build signature parameters
    String buildSignMessage = PayKit.buildSignMessage(method, urlSuffix, timestamp, nonceStr, body);
    // Get merchant private key
    String key = PayKit.getPrivateKey(keyPath);
    // Generate signature
    String signature = RsaKit.encryptByPrivateKey(buildSignMessage, key);
    // Generate request header authorization according to platform rules
    return PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType);
}


/**
 * Access to authorized certification information
 *
 * @param mchId     Merchant number
 * @param serialNo  Merchant API certificate serial number
 * @param nonceStr  Request random string
 * @param timestamp time stamp
 * @param signature Signature value
 * @param authType  Certification type, currently WECHATPAY2-SHA256-RSA2048
 * @return Request header Authorization
 */
public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) {
    Map<String, String> params = new HashMap<>(5);
    params.put("mchid", mchId);
    params.put("serial_no", serialNo);
    params.put("nonce_str", nonceStr);
    params.put("timestamp", timestamp);
    params.put("signature", signature);
    return authType.concat(" ").concat(createLinkString(params, ",", false, true));
}

Splicing parameter

    public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            // When splicing, the last & character is not included
            if (i == keys.size() - 1) {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value);
                }
            } else {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
                }
            }
        }
        return content.toString();
    }

From the above example, we still have two parameters

  • Serial no. of certificate
  • signature uses merchant private key to sign SHA256 with RSA

How to get it? Don't worry, let me have a "coffee of 1989" to refresh myself.

Get certificate serial number

Obtained through tools

Get by code

// Get certificate serial number
X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream("apiclient_cert.pem Certificate path"));

System.out.println("Output certificate information:\n" + certificate.toString());
System.out.println("Certificate serial number:" + certificate.getSerialNumber().toString(16));
System.out.println("Version number:" + certificate.getVersion());
System.out.println("Issuer:" + certificate.getIssuerDN());
System.out.println("Effective from:" + certificate.getNotBefore());
System.out.println("Effective end date:" + certificate.getNotAfter());
System.out.println("Body name:" + certificate.getSubjectDN());
System.out.println("Signature algorithm:" + certificate.getSigAlgName());
System.out.println("Signature:" + certificate.getSignature().toString());


/**
 * Obtaining certificates
 *
 * @param inputStream Certificate file
 * @return {@link X509Certificate} Obtaining certificates
 */
public static X509Certificate getCertificate(InputStream inputStream) {
    try {
        CertificateFactory cf = CertificateFactory.getInstance("X509");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
        cert.checkValidity();
        return cert;
    } catch (CertificateExpiredException e) {
        throw new RuntimeException("Certificate expired", e);
    } catch (CertificateNotYetValidException e) {
        throw new RuntimeException("Certificate is not yet valid", e);
    } catch (CertificateException e) {
        throw new RuntimeException("Invalid certificate", e);
    }
}

SHA256 with RSA signature

Get merchant private key

 /**
  * Get merchant private key
  *
  * @param keyPath Merchant private key certificate path
  * @return Merchant private key
  * @throws Exception Parse key exception
  */
 public static String getPrivateKey(String keyPath) throws Exception {
     String originalKey = FileUtil.readUtf8String(keyPath);
     String privateKey = originalKey
             .replace("-----BEGIN PRIVATE KEY-----", "")
             .replace("-----END PRIVATE KEY-----", "")
             .replaceAll("\\s+", "");
     return RsaKit.getPrivateKeyStr(RsaKit.loadPrivateKey(privateKey));
 }


public static String getPrivateKeyStr(PrivateKey privateKey) {
    return Base64.encode(privateKey.getEncoded());
}

 
 /**
  * Load private key from string
  *
  * @param privateKeyStr Private key
  * @return {@link PrivateKey}
  * @throws Exception Abnormal information
  */
 public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
     try {
         byte[] buffer = Base64.decode(privateKeyStr);
         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
         KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
         return keyFactory.generatePrivate(keySpec);
     } catch (NoSuchAlgorithmException e) {
         throw new Exception("No algorithm is available.");
     } catch (InvalidKeySpecException e) {
         throw new Exception("Private key illegal");
     } catch (NullPointerException e) {
         throw new Exception("Private key data is empty");
     }
 }

Private key signature

/**
  * Private key signature
  *
  * @param data       Data to be encrypted
  * @param privateKey Private key
  * @return Encrypted data
  * @throws Exception Abnormal information
  */
 public static String encryptByPrivateKey(String data, String privateKey) throws Exception {
     PKCS8EncodedKeySpec priPkcs8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
     KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
     PrivateKey priKey = keyFactory.generatePrivate(priPkcs8);
     java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");

     signature.initSign(priKey);
     signature.update(data.getBytes(StandardCharsets.UTF_8));
     byte[] signed = signature.sign();
     return StrUtil.str(Base64.encode(signed));
 }

So far, wechat payment Api-v3 interface request parameters have been encapsulated.

Execution request

/**
 * V3 Interface unified execution entry
 *
 * @param method    {@link RequestMethod} Request method
 * @param urlPrefix Available through {@ link WxDomain}
 * @param urlSuffix It can be obtained through {@ link WxApiType}, and the URL mounting parameters need to be spliced by themselves
 * @param mchId     Merchant Id
 * @param serialNo  Merchant API certificate serial number
 * @param keyPath   apiclient_key.pem Certificate path
 * @param body      Interface request parameters
 * @param nonceStr  Random character library
 * @param timestamp time stamp
 * @param authType  Authentication type
 * @param file      file
 * @return {@link String} Results returned by the request
 * @throws Exception Interface execution exception
 */
public static Map<String, Object> v3Execution(RequestMethod method, String urlPrefix, String urlSuffix,
                                              String mchId, String serialNo, String keyPath, String body,
                                              String nonceStr, long timestamp, String authType,
                                              File file) throws Exception {
    // Build Authorization
    String authorization = WxPayKit.buildAuthorization(method, urlSuffix, mchId, serialNo,
            keyPath, body, nonceStr, timestamp, authType);

    if (method == RequestMethod.GET) {
        return doGet(urlPrefix.concat(urlSuffix), authorization, serialNo, null);
    } else if (method == RequestMethod.POST) {
        return doPost(urlPrefix.concat(urlSuffix), authorization, serialNo, body);
    } else if (method == RequestMethod.DELETE) {
        return doDelete(urlPrefix.concat(urlSuffix), authorization, serialNo, body);
    } else if (method == RequestMethod.UPLOAD) {
        return doUpload(urlPrefix.concat(urlSuffix), authorization, serialNo, body, file);
    }
    return null;
}

By default, the network request library is implemented by using a set of Java tools encapsulated by Hutool

GET request

/**
 * @param url           Request url
 * @param authorization Authorization information
 * @param serialNumber  Public key certificate serial number
 * @param jsonData      Request parameters
 * @return {@link HttpResponse} Results returned by request
 */
private HttpResponse doGet(String url, String authorization, String serialNumber, String jsonData) {
    return HttpRequest.post(url)
            .addHeaders(getHeaders(authorization, serialNumber))
            .body(jsonData)
            .execute();
}

POST request

 /**
  * @param url           Request url
  * @param authorization Authorization information
  * @param serialNumber  Public key certificate serial number
  * @param jsonData      Request parameters
  * @return {@link HttpResponse} Results returned by request
  */
 private HttpResponse doPost(String url, String authorization, String serialNumber, String jsonData) {
     return HttpRequest.post(url)
             .addHeaders(getHeaders(authorization, serialNumber))
             .body(jsonData)
             .execute();
 }

DELETE request

/**
 * delete request
 *
 * @param url           Request url
 * @param authorization Authorization information
 * @param serialNumber  Public key certificate serial number
 * @param jsonData      Request parameters
 * @return {@link HttpResponse} Results returned by request
 */
private HttpResponse doDelete(String url, String authorization, String serialNumber, String jsonData) {
    return HttpRequest.delete(url)
            .addHeaders(getHeaders(authorization, serialNumber))
            .body(jsonData)
            .execute();
}

Upload files

 /**
   * @param url           Request url
   * @param authorization Authorization information
   * @param serialNumber  Public key certificate serial number
   * @param jsonData      Request parameters
   * @param file          Uploaded files
   * @return {@link HttpResponse} Results returned by request
   */
  private HttpResponse doUpload(String url, String authorization, String serialNumber, String jsonData, File file) {
      return HttpRequest.post(url)
              .addHeaders(getUploadHeaders(authorization, serialNumber))
              .form("file", file)
              .form("meta", jsonData)
              .execute();
  }

Build Http request header

private Map<String, String> getBaseHeaders(String authorization) {
    String userAgent = String.format(
            "WeChatPay-IJPay-HttpClient/%s (%s) Java/%s",
            getClass().getPackage().getImplementationVersion(),
            OS,
            VERSION == null ? "Unknown" : VERSION);

    Map<String, String> headers = new HashMap<>(3);
    headers.put("Accept", ContentType.JSON.toString());
    headers.put("Authorization", authorization);
    headers.put("User-Agent", userAgent);
    return headers;
}

private Map<String, String> getHeaders(String authorization, String serialNumber) {
    Map<String, String> headers = getBaseHeaders(authorization);
    headers.put("Content-Type", ContentType.JSON.toString());
    if (StrUtil.isNotEmpty(serialNumber)) {
        headers.put("Wechatpay-Serial", serialNumber);
    }
    return headers;
}

private Map<String, String> getUploadHeaders(String authorization, String serialNumber) {
    Map<String, String> headers = getBaseHeaders(authorization);
    headers.put("Content-Type", "multipart/form-data;boundary=\"boundary\"");
    if (StrUtil.isNotEmpty(serialNumber)) {
        headers.put("Wechatpay-Serial", serialNumber);
    }
    return headers;
}

Build Http request return value

Get wechat response header information, status code and body from the HttpResponse of the response

/**
 * Build return parameters
 *
 * @param httpResponse {@link HttpResponse}
 * @return {@link Map}
 */
private Map<String, Object> buildResMap(HttpResponse httpResponse) {
    Map<String, Object> map = new HashMap<>();
    String timestamp = httpResponse.header("Wechatpay-Timestamp");
    String nonceStr = httpResponse.header("Wechatpay-Nonce");
    String serialNo = httpResponse.header("Wechatpay-Serial");
    String signature = httpResponse.header("Wechatpay-Signature");
    String body = httpResponse.body();
    int status = httpResponse.getStatus();

    map.put("timestamp", timestamp);
    map.put("nonceStr", nonceStr);
    map.put("serialNumber", serialNo);
    map.put("signature", signature);
    map.put("body", body);
    map.put("status", status);

    return map;
}

Now that you have finished building the request parameters, execute the request. Next, we need to decrypt the response data and verify the signature of the response result

Corresponding official documents

Verifying signature

Build signature parameters

/**
 * Construct signature string
 *
 * @param timestamp Response timestamp
 * @param nonceStr  Response random string
 * @param body      Response message body
 * @return Answer to signature string
 */
public static String buildSignMessage(String timestamp, String nonceStr, String body) {
    return new StringBuilder()
            .append(timestamp)
            .append("\n")
            .append(nonceStr)
            .append("\n")
            .append(body)
            .append("\n")
            .toString();
}

Certificate and callback message decryption

There is a complete source code at the end of the official document, and it will not be pasted here. Post an example. Let's talk about the parameters

try {
      String associatedData = "certificate";
      String nonce = "80d28946a64a";
      String cipherText = "DwAqW4+4TeUaOEylfKEXhw+XqGh/YTRhUmLw/tBfQ5nM9DZ9d+9aGEghycwV1jwo52vXb/t6ueBvBRHRIW5JgDRcXmTHw9IMTrIK6HxTt2qiaGTWJU9whsF+GGeQdA7gBCHZm3AJUwrzerAGW1mclXBTvXqaCl6haE7AOHJ2g4RtQThi3nxOI63/yc3WaiAlSR22GuCpy6wJBfljBq5Bx2xXDZXlF2TNbDIeodiEnJEG2m9eBWKuvKPyUPyClRXG1fdOkKnCZZ6u+ipb4IJx28n3MmhEtuc2heqqlFUbeONaRpXv6KOZmH/IdEL6nqNDP2D7cXutNVCi0TtSfC7ojnO/+PKRu3MGO2Z9q3zyZXmkWHCSms/C3ACatPUKHIK+92MxjSQDc1E/8faghTc9bDgn8cqWpVKcL3GHK+RfuYKiMcdSkUDJyMJOwEXMYNUdseQMJ3gL4pfxuQu6QrVvJ17q3ZjzkexkPNU4PNSlIBJg+KX61cyBTBumaHy/EbHiP9V2GeM729a0h5UYYJVedSo1guIGjMZ4tA3WgwQrlpp3VAMKEBLRJMcnHd4pH5YQ/4hiUlHGEHttWtnxKFwnJ6jHr3OmFLV1FiUUOZEDAqR0U1KhtGjOffnmB9tymWF8FwRNiH2Tee/cCDBaHhNtfPI5129SrlSR7bZc+h7uzz9z+1OOkNrWHzAoWEe3XVGKAywpn5HGbcL+9nsEVZRJLvV7aOxAZBkxhg8H5Fjt1ioTJL+qXgRzse1BX1iiwfCR0fzEWT9ldDTDW0Y1b3tb419MhdmTQB5FsMXYOzqp5h+Tz1FwEGsa6TJsmdjJQSNz+7qPSg5D6C2gc9/6PkysSu/6XfsWXD7cQkuZ+TJ/Xb6Q1Uu7ZB90SauA8uPQUIchW5zQ6UfK5dwMkOuEcE/141/Aw2rlDqjtsE17u1dQ6TCax/ZQTDQ2MDUaBPEaDIMPcgL7fCeijoRgovkBY92m86leZvQ+HVbxlFx5CoPhz4a81kt9XJuEYOztSIKlm7QNfW0BvSUhLmxDNCjcxqwyydtKbLzA+EBb2gG4ORiH8IOTbV0+G4S6BqetU7RrO+/nKt21nXVqXUmdkhkBakLN8FUcHygyWnVxbA7OI2RGnJJUnxqHd3kTbzD5Wxco4JIQsTOV6KtO5c960oVYUARZIP1SdQhqwELm27AktEN7kzg/ew/blnTys/eauGyw78XCROb9F1wbZBToUZ7L+8/m/2tyyyqNid+sC9fYqJoIOGfFOe6COWzTI/XPytCHwgHeUxmgk7NYfU0ukR223RPUOym6kLzSMMBKCivnNg68tbLRJHEOpQTXFBaFFHt2qpceJpJgw5sKFqx3eQnIFuyvA1i8s2zKLhULZio9hpsDJQREOcNeHVjEZazdCGnbe3Vjg7uqOoVHdE/YbNzJNQEsB3/erYJB+eGzyFwFmdAHenG5RE6FhCutjszwRiSvW9F7wvRK36gm7NnVJZkvlbGwh0UHr0pbcrOmxT81xtNSvMzT0VZNLTUX2ur3AGLwi2ej8BIC0H41nw4ToxTnwtFR1Xy55+pUiwpB7JzraA08dCXdFdtZ72Tw/dNBy5h1P7EtQYiKzXp6rndfOEWgNOsan7e1XRpCnX7xoAkdPvy40OuQ5gNbDKry5gVDEZhmEk/WRuGGaX06CG9m7NfErUsnQYrDJVjXWKYuARd9R7W0aa5nUXqz/Pjul/LAatJgWhZgFBGXhNr9iAoade/0FPpBj0QWa8SWqKYKiOqXqhfhppUq35FIa0a1Vvxcn3E38XYpVZVTDEXcEcD0RLCu/ezdOa6vRcB7hjgXFIRZQAka0aXnQxwOZwE2Rt3yWXqc+Q1ah2oOrg8Lg3ETc644X9QP4FxOtDwz/A==";

      AesUtil aesUtil = new AesUtil(wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
      // Decryption of platform certificate ciphertext
      // Associated? Data nonce ciphertext in encrypt? Certificate
      String publicKey = aesUtil.decryptToString(
              associatedData.getBytes(StandardCharsets.UTF_8),
              nonce.getBytes(StandardCharsets.UTF_8),
              cipherText
      );
      // Certificate of preservation
      FileWriter writer = new FileWriter(wxPayV3Bean.getPlatformCertPath());
      writer.write(publicKey);
      // Get platform certificate serial number
      X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
      return certificate.getSerialNumber().toString(16).toUpperCase();
  } catch (Exception e) {
      e.printStackTrace();
  }

Verifying signature

/**
 * Verifying signature
 *
 * @param signature       Signature to be verified
 * @param body            Responder
 * @param nonce           Random strings
 * @param timestamp       time stamp
 * @param certInputStream Wechat payment platform certificate input stream
 * @return Signature result
 * @throws Exception Abnormal information
 */
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception {
    String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);
    // Obtaining certificates
    X509Certificate certificate = PayKit.getCertificate(certInputStream);
    PublicKey publicKey = certificate.getPublicKey();
    return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
}

/**
 * Public key verification signature
 *
 * @param data      Data to be encrypted
 * @param sign      autograph
 * @param publicKey Public key
 * @return Verification results
 * @throws Exception Abnormal information
 */
public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {
    java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
    signature.initVerify(publicKey);
    signature.update(data.getBytes(StandardCharsets.UTF_8));
    return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8)));
}

So far, wechat payment Api-v3 interface has been introduced. If you have any questions, please leave a message to discuss.

Full example SpringBoot

Reference material

96 original articles published, 15.5 praised, 540000 visitors+
His message board follow

Tags: Java JSON xml encoding

Posted on Fri, 06 Mar 2020 02:37:13 -0800 by Sam