springboot integrates wechat payment V3 and uses JSAPI to order the whole process. It is easy to read without calling a third-party package (complete source code) and can be enjoyed out of the box

@TOC

preface

At present, due to the need of the project, a wechat payment function is used for wechat applet. I've been in a hurry for a long time. I'm confused about wechat official documents and many Baidu blogs. I don't know where to do it! Either wechat documents are too official, various encryption and decryption are too cumbersome, or excellent open source projects such as IJPay are too packaged. I don't know how to troubleshoot problems! Therefore, I grabbed my hair, summarized the experience of major bloggers and wechat official documents, and worked hard to come up with the following code applicable to wechat V3 payment JSAPI, which can be enjoyed by all bosses! Complete comments and source code, and you can get on the bus after posting;

Tip: This article mainly implements the ordering of V3 version JSAPI, which can be used on the applet side! Applet side use! For other APP or native code scanning payment, please ask another expert

Official API document of wechat payment
Official account of WeChat official small program public number developer registration

1, Direct up flow chart

1. apply for small procedures or official account, and register as merchant to get payment parameter steps.

2. Call JSAPI to start wechat payment process

2, If you don't say much, you can write directly. The preparation for the first step is complete. All information parameters will be explained later

1. Create the springboot project and introduce the following dependencies

  		<!--json analysis-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <!--http Dependency send request-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5.2</version>
        </dependency>
        <!-- Confused Toolkit-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.2</version>
        </dependency>

        <!--lombok reduce getset-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--xml Read tool-->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-avro</artifactId>
        </dependency>

2. Create wxpay under project resource_ V3 payment parameters are configured in v3.properties file

#appid payment of business related small program ID or official account ID (landing WeChat public platform > Development > basic configuration > developer ID(AppID))
v3.appId=wx172hvs19744c8653
# Corresponding apiclient_ The path of key.pem (merchant platform - > account center - > Certificate - > after downloading, there are three in total: mainly apiclient_key.pem). Put the file in the project directory during development, put it in the project root directory when it is launched, and do not package it together
v3.keyPath=/Users/apiclient_key.pem
#Wechat payment merchant No. (login merchant platform – > account center – > merchant information – > wechat payment merchant No.)
v3.mchId=1234399709
#V3 secret key (merchant platform - > account center - > V3 secret key - > set 32 bits)
v3.v3Key=14eoil2d39vs452084b1c3f3c3131339
#Certificate serial number (this is the specific code returned by the wechat payment interface according to the key file and other merchant parameters. Call it once to write and save it)
v3.mchSerialNo=48D429FF87CC967B09EB6219C9382A0CC4A79D66
#The wechat notification URL is the wechat callback URL after the order is successfully placed. Only https domain names are supported, so peanut shells can be used for mapping during development
v3.notifyOrderUrl=https://youdomain.com/app/pay/callback
#The refund URL is self configured as above
v3.notifyRefoundUrl=https://youdomian.com
#The refund callback URL is self configured as above
v3.returnUrl=https://youdomian.com
#Small program or official account secret key (landing WeChat public platform > Development > basic configuration - >secret)
v3.secret=t23eb5fae1cb58a1ff4d00987ee12344

Warm tip: the above information is desensitized and needs to be modified according to your own

3. Write a file to read the above configuration. You can also put it in YML for reading, and create WxPayConfig.class configuration class

package com.matinzac.config;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>Description: Encapsulate the configuration class for reading wechat payment configuration information</p>
 *
 * @author MtinZac
 * create on 2021/11/26 10:30 am
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
@PropertySource("classpath:/wxpay_v3.properties")
@ConfigurationProperties(prefix = "v3")
public class WxPayConfig {
    /**
     * Applet ID
     */
    private String appId;
    /**
     * client_key route
     */
    private String keyPath;
    /**
     * Merchant ID
     */
    private String mchId;
    /**
     * V3 key Secret key
     */
    private String v3Key;
    /**
     * API Certificate serial number
     */
    private String mchSerialNo;
    /**
     * Payment success callback URL
     */
    private String notifyOrderUrl;
    /**
     * Refund callback path
     */
    private String notifyRefoundUrl;
    /**
     * Refund successful return path
     */
    private String returnUrl;

    /**
     * Developer key
     */
    private String secret;


    /**
     * // Define global container to save wechat platform certificate public key
     */
    public Map<String, X509Certificate> certificateMap = new ConcurrentHashMap<>();

}

4. Write constants constant class to declare wechat related interface addresses

package com.matinzac.constants;

/**
 * <p>Description: Wechat payment related URL general class</p>
 *
 * @author MtinZac
 * create on 2021/11/24 11:14 am
 */
public class WxPayConstants {


    /**
     * Wechat code for openid and unionid URL s
     */
    public static final String COUDE_OPENID_URL ="https://api.weixin.qq.com/sns/jscode2session?appid=";


    /**
     * Applicable to: applet orders
     * Request method: POST
     */
    public static final String JSAPIURL = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";

    /**
     * Applicable object: APP
     * Request method: POST
     */
    public static final String APPURL = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
    /**
     * Get certificate
     */
    public static final String CERTIFICATESURL = "https://api.mch.weixin.qq.com/v3/certificates";

    /**
     * Refund address
     */
    public static final String REFUNDSURL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";

}

5. Write decryption tools according to wechat official instructions

package com.matinzac.util;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * <p>Description:The decryption tool class is used to decrypt wechat requests and return information</p>
 *
 * @author MtinZac
 * create on  2021/11/26 10:40 am
 */
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("invalid ApiV3Key,The length must be 32 bytes");
        }
        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);
        }
    }
}

6. Write the JSON conversion class to provide the conversion of the return parameters of the calling interface

package com.sbm.paydemp.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sbm.paydemp.config.WechatUrlConfig;
import com.sbm.paydemp.config.WxpayConfig;
import com.sbm.paydemp.entity.Product;
import com.sbm.paydemp.utils.HttpUtils;
import com.sbm.paydemp.utils.WechatPayUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * <p>Description:</p>
 *
 * @author MaYongHui
 * create on 2021/11/23 10:07 am
 */
public class WxPayService {

    public Map<String, Object> wxPay(String openid, Integer type, Product product) throws JsonProcessingException {
        Map<String, Object> map = new HashMap();
        // The products that are paid (small program or official account) need to be bound with WeChat payment.
        map.put("appid", WxpayConfig.app_id);
        // Merchant number of payment
        map.put("mchid", WxpayConfig.mch_id);
        //Temporary write dead configuration
        map.put("description", product.getSubject());
        map.put("out_trade_no", product.getOutTradeNo());
        map.put("notify_url", WxpayConfig.notify_order_url);

        Map<String, Object> amount = new HashMap();
        //Order amount unit: Fen
        amount.put("total", Integer.parseInt(product.getTotalFee()) * 100);
        amount.put("currency", "CNY");
        map.put("amount", amount);
        // Set the opendi required by the applet
        Map<String, Object> payermap = new HashMap();
        payermap.put("openid", openid);
        map.put("payer", payermap);

        ObjectMapper objectMapper = new ObjectMapper();
        String body = objectMapper.writeValueAsString(map);

        Map<String, Object> stringObjectMap = null;
        HashMap<String, Object> dataMap = null;
        try {

            stringObjectMap = HttpUtils.doPostWexin(WechatUrlConfig.JSAPIURL, body);
            dataMap = WechatPayUtils.getTokenJSAPI(WxpayConfig.app_id, String.valueOf(stringObjectMap.get("prepay_id")));

            return dataMap;
        } catch (Exception ex) {
        }
        return null;
    }


}

7. Write service declaration interface

package com.matinzac.service;


import com.matinzac.entity.PayOrder;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
 * <p>Description: Wechat payment related interfaces</p>
 *
 * @author MtinZac
 * create on 2021/11/26 10:40 am
 */
public interface WxPayService {

    /**
     * Get oppenId interface through code
     * @return Return the intercepted openid from wechat
     */
    String getOpenId(String code);

    /**
     * Return the request payment parameters through oppenId and payment related information
     * @param openid client ID 
     * @param time_expire Order expiration time
     * @param order Order information
     * @return
     */
    Map<String, Object> getPayInfo(String openid,String time_expire, PayOrder order);

    /**
     * Payment success callback interface
     * @param body Wechat payment return parameter information
     * @param request  Request body
     * @return Analyze the return parameters for feedback
     */
    Map<String, Object> callBack(Map body, HttpServletRequest request);
}

8. Write the specific call implementation of the implementation class. Since my config reads the configuration file and uses the injection method, the encryption and decryption are written in the implementation class so that injection can be used directly. It is not proposed to be encapsulated in util, but the comments are clear and easy to understand

package com.matinzac.service.impl;

import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.matinzac.config.WxPayConfig;
import com.matinzac.constants.WxPayConstants;
import com.matinzac.entity.PayOrder;
import com.matinzac.service.WxPayService;
import com.matinzac.util.*;
import com.matinzac.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

/**
 * <p>Description:Specific implementation of wechat payment</p>
 *
 * @author MtinZac
 * create on  2021/11/26 10:40 am
 */
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {

    @Resource
    private WxPayConfig wxPayConfig;


    @Override
    public String getOpenId(String code) {
        log.info("obtain code:{}", code);
        String url = WxPayConstants.COUDE_OPENID_URL
                + wxPayConfig.getAppId() + "&secret=" + wxPayConfig.getSecret() + "&js_code=" + code + "&grant_type=authorization_code";
        String res = HttpUtil.get(url);
        System.out.println(res);
        JSONObject object = JSONObject.parseObject(res);
        String openid = object.getString("openid");
        String unionid = object.getString("unionid");
        log.info("according to code exchange for openId:{}", openid);

        return openid;
    }

    @Override
    public Map<String, Object> getPayInfo(String openid, String time_expire, PayOrder order) {
        Map<String, Object> map = new HashMap();
        // The products that are paid (small program or official account) need to be bound with WeChat payment.
        map.put("appid", wxPayConfig.getAppId());
        // Merchant number of payment
        map.put("mchid", wxPayConfig.getMchId());
        //Temporary write dead configuration
        map.put("description", order.getRemarkBody());
        map.put("out_trade_no", order.getOutTradeNo());
        map.put("notify_url", wxPayConfig.getNotifyOrderUrl());

        //Judge whether the expiration time is null. If there is an expiration time, add it
        if (!StringUtils.isEmpty(time_expire)) {
            map.put("time_expire", time_expire);
        }

        Map<String, Object> amount = new HashMap();
        //The order amount unit is Fen, which needs to be converted from * 100 to yuan
        amount.put("total", Integer.parseInt(order.getTotalFee()) * 100);
        amount.put("currency", "CNY");
        map.put("amount", amount);
        // Set the opendi required by the applet
        Map<String, Object> payermap = new HashMap();
        payermap.put("openid", openid);
        map.put("payer", payermap);

        ObjectMapper objectMapper = new ObjectMapper();
        String body = null;
        try {
            body = objectMapper.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        Map<String, Object> stringObjectMap = null;
        HashMap<String, Object> dataMap = null;
        try {
            //Get signature token
            String token = getToken("POST", new URL(WxPayConstants.JSAPIURL), body);
            //Send payment request
            stringObjectMap = HttpUtils.doPostWexin(WxPayConstants.JSAPIURL, body, token);
            log.info("Obtain payment voucher information as:" + stringObjectMap);
            dataMap = getTokenJSAPI(wxPayConfig.getAppId(), String.valueOf(stringObjectMap.get("prepay_id")));
            log.info("The return payment parameters are:" + dataMap);
            return dataMap;
        } catch (Exception ex) {
            log.info("Return payment information body exception");
        }
        return null;
    }

    @Override
    public Map<String, Object> callBack(Map body, HttpServletRequest request) {
        log.info(DateUtils.getTime() + "Wechat payment callback started");
        Map<String, Object> result = new HashMap();
        //1: Get the signature information of wechat payment callback
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            // 2: Start analyzing newspaper style
            String data = objectMapper.writeValueAsString(body);
            String message = timestamp + "\n" + nonce + "\n" + data + "\n";
            //3: Get reply signature
            String sign = request.getHeader("Wechatpay-Signature");
            //4: Obtain the certificate corresponding to the platform
            String serialNo = request.getHeader("Wechatpay-Serial");
            if (!wxPayConfig.certificateMap.containsKey(serialNo)) {
                //Get the token verification certificate that verifies the signature
                String token = getToken("GET", new URL(WxPayConstants.CERTIFICATESURL), "");
                wxPayConfig.certificateMap = refreshCertificate(token);
            }

            //Get certificate number and verify signature
            X509Certificate x509Certificate = wxPayConfig.certificateMap.get(serialNo);
            if (!verify(x509Certificate, message.getBytes(), sign)) {
                throw new IllegalArgumentException("Wechat payment signature verification failed:" + message);
            }
            //    log.info("signature verification succeeded");
            Map<String, String> resource = (Map) body.get("resource");
            // 5: Callback message decryption
            AesUtil aesUtil = new AesUtil(wxPayConfig.getV3Key().getBytes());
            //Decrypted json string
            String decryptToString = aesUtil.decryptToString(
                    resource.get("associated_data").getBytes(),
                    resource.get("nonce").getBytes(),
                    resource.get("ciphertext"));
            log.info("The returned string is before decryption------------->decryptToString====>{}", decryptToString);

            //6: Get the information returned by wechat payment
            Map<String, Object> jsonData = objectMapper.readValue(decryptToString, Map.class);
            log.info(DateUtils.getTime() + "The data carried by wechat callback after decryption is:");
            //7: If the judgment of payment status is success, it means that the payment is successful
            // 8: Obtain the payment transaction No., serial no., and auxiliary parameters
            String out_trade_no = jsonData.get("out_trade_no").toString();
            log.info("The order number for payment is:" + out_trade_no);
            if ("SUCCESS".equals(jsonData.get("trade_state"))) {
                //Serial number
                String transaction_id = jsonData.get("transaction_id").toString();
                String success_time = jsonData.get("success_time").toString();
                String attach = jsonData.get("attach").toString();
                //Query the payment status according to the order number. If it is not paid, update the payment status to paid
                // TODO updates its order status to paid and stores the payment information (saves the payment information)


            } else {
                //In case of payment failure or cancellation
                // TODO payment fails or the order status is modified in other status, or Lu Aojiao is processed according to the error status code of wechat
            }
            result.put("code", "SUCCESS");
            result.put("message", "success");
        } catch (Exception e) {
            result.put("code", "fail");
            result.put("message", "System error");
            e.printStackTrace();
        }
        return result;
    }


    /**
     * Reference website https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
     * Calculate signature value
     *
     * @param appId     According to openId
     * @param prepay_id The pre payment transaction session ID (calling the JSAPI order interface with payment parameters) returns the transaction identifier
     * @return
     * @throws IOException
     * @throws SignatureException
     * @throws NoSuchAlgorithmException
     * @throws
     */
    public HashMap<String, Object> getTokenJSAPI(String appId, String prepay_id) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        // Get random string
        String nonceStr = getNonceStr();
        // Get wechat applet payment package
        String packagestr = "prepay_id=" + prepay_id;
        long timestamp = System.currentTimeMillis() / 1000;
        //Signature: the signature value calculated by using the fields appId, timeStamp, nonceStr and package
        String message = buildMessageTwo(appId, timestamp, nonceStr, packagestr);
        //Get the corresponding signature
        String signature = sign(message.getBytes("utf-8"));
        // Assembly return
        HashMap<String, Object> map = new HashMap<>();
        map.put("appId", appId);
        map.put("timeStamp", String.valueOf(timestamp));
        map.put("nonceStr", nonceStr);
        map.put("package", packagestr);
        map.put("signType", "RSA");
        map.put("paySign", signature);
        return map;
    }

    /**
     * Get private key
     *
     * @param filename Private key file path (required)
     * @return Private key object
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {
        System.out.println("filename:" + filename);
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("current Java Environment not supported RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("Invalid key format");
        }
    }

    /**
     * Generating a token means generating a signature
     *
     * @param method Request type (GET, POST)
     * @param url    The URL s for obtaining the token are: https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi   Query certificate: https://api.mch.weixin.qq.com/v3/certificates
     * @param body   The encapsulated payment voucher information includes merchant ID,APPID and certificate serial number
     * @return
     * @throws Exception
     */
    public String getToken(String method, URL url, String body) throws Exception {
        String nonceStr = UUID.getNonceStr();
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("utf-8"));

        return "WECHATPAY2-SHA256-RSA2048 " + "mchid=\"" + wxPayConfig.getMchId() + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + wxPayConfig.getMchSerialNo() + "\","
                + "signature=\"" + signature + "\"";
    }


    /**
     * Obtain the platform certificate by carrying the token
     *
     * @param token Get the token of the certificate https://api.mch.weixin.qq.com/v3/certificates
     * @return
     * @throws Exception
     */
    public Map<String, X509Certificate> refreshCertificate(String token) throws Exception {
        Map<String, X509Certificate> certificateMap = new HashMap();
        // 1: Execute get request
        JsonNode jsonNode = HttpUtils.doGet(WxPayConstants.CERTIFICATESURL, token);
        // 2: Obtain relevant parameter information of platform verification
        JsonNode data = jsonNode.get("data");
        if (data != null) {
            for (int i = 0; i < data.size(); i++) {
                JsonNode encrypt_certificate = data.get(i).get("encrypt_certificate");
                //Decrypt key information
                AesUtil aesUtil = new AesUtil(wxPayConfig.getV3Key().getBytes());
                String associated_data = encrypt_certificate.get("associated_data").toString().replaceAll("\"", "");
                String nonce = encrypt_certificate.get("nonce").toString().replaceAll("\"", "");
                String ciphertext = encrypt_certificate.get("ciphertext").toString().replaceAll("\"", "");
                //Certificate content
                String certStr = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
                //Convert certificate content to certificate object
                CertificateFactory cf = CertificateFactory.getInstance("X509");
                X509Certificate x509Cert = (X509Certificate) cf.generateCertificate(
                        new ByteArrayInputStream(certStr.getBytes("utf-8"))
                );
                String serial_no = data.get(i).get("serial_no").toString().replaceAll("\"", "");
                certificateMap.put(serial_no, x509Cert);
            }
        }
        return certificateMap;
    }


    /**
     * Generate signature information
     *
     * @param method    Request type (GET, POST)
     * @param url       Requested URL
     * @param timestamp time stamp
     * @param nonceStr  32 Bit random string
     * @param body      The encapsulated payment voucher information includes merchant ID,APPID and certificate serial number
     * @return
     */
    public static String buildMessage(String method, URL url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.getPath();
        if (url.getQuery() != null) {
            canonicalUrl += "?" + url.getQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * Generate 32-bit random string
     *
     * @return 32 Bit random string
     */
    public static String getNonceStr() {
        return UUID.randomUUID().toString()
                .replaceAll("-", "")
                .substring(0, 32);
    }

    /**
     * Verify signature
     *
     * @param certificate
     * @param message
     * @param signature
     * @return
     */
    public static boolean verify(X509Certificate certificate, byte[] message, String signature) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initVerify(certificate);
            sign.update(message);
            return sign.verify(Base64.getDecoder().decode(signature));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("current Java Environment not supported SHA256withRSA", e);
        } catch (SignatureException e) {
            throw new RuntimeException("An error occurred during signature verification", e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("Invalid certificate", e);
        }
    }


    /**
     * Splicing request parameters
     *
     * @param appId     Applet id
     * @param timestamp time stamp
     * @param nonceStr  Notification random string
     * @param packag
     * @return
     */
    private static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) {
        return appId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + packag + "\n";
    }

    /**
     * Get the private key file and generate a signature
     *
     * @param message
     * @return
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws IOException
     * @throws InvalidKeyException
     */
    private String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
        Signature sign = Signature.getInstance("SHA256withRSA"); //SHA256withRSA
        sign.initSign(getPrivateKey(wxPayConfig.getKeyPath()));
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }
}

9. Write the controller interface

package com.matinzac.controller;


import com.matinzac.entity.PayOrder;
import com.matinzac.res.AjaxResult;
import com.matinzac.service.WxPayService;
import com.matinzac.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * <p>Description: Wechat payment related interfaces</p>
 *
 * @author MtinZac
 * create on 2021/11/26 10:40 am
 */
@Slf4j
@CrossOrigin
@RequestMapping("/app")
@RestController
public class WxPayController {

    @Resource
    private WxPayService payService;




    /**
     * Exchange openId through code
     *
     * @param code Wechat client code value
     * @return openId returned
     */
    @GetMapping("/getOpenId/{code}")
    public AjaxResult getOpenId(@PathVariable String code) {
        if (StringUtils.isEmpty(code)) {
            AjaxResult.error("code Cannot be empty");
        }
        String openId = payService.getOpenId(code);
        return AjaxResult.success().put("openId", openId);
    }


    /**
     * Obtain payment information parameters through openId and activity id
     * Wechat payment jsapi document https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
     * @param openid Client unique identifier
     * @param aid    Activity ID
     * @return Call up relevant parameters of applet payment (including session id and order ID)
     */
    @GetMapping("/getPayInfo/{openid}/{aid}")
    public AjaxResult getPayInfo(@PathVariable("openid") String openid, @PathVariable("aid") String aid) {
        if (StringUtils.isEmpty(openid)) {
            AjaxResult.error("openid Cannot be empty");
        }
        if (StringUtils.isEmpty(aid)) {
            AjaxResult.error("aid Cannot be empty");
        }

        // TODO queries the order payment status to prevent overpayment for the same order or the same commodity

        PayOrder payOrder = new PayOrder();

        // TODO needs to set the order timeout here, which can be configured here. dateUtils provides relevant methods for time format conversion. For details, refer to
        // Wechat JSAPI payment order timeout description https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml

        
        //Get payment parameters and return
        Map<String, Object> payInfo = payService.getPayInfo(openid, null, payOrder);

        //Create an order after successfully obtaining the payment return information
        // TODO stores the order information
        //Return payment parameters to wechat payment
        return AjaxResult.success().put("payInfo", payInfo).put("order", payOrder);
    }

    /**
     * Payment success callback interface
     *
     * @param body    Wechat return parameter body
     * @param request Request body
     * @return Processing information returned to wechat
     */
    @PostMapping("/pay/callback")
    public Map orderPayCallback(@RequestBody Map body, HttpServletRequest request) {
        return payService.callBack(body, request);
    }
}

The above is all the source code for calling wechat JSAPI payment. Some utils are not released, but they are very simple and can be implemented by themselves (DateUtils,StringUtils,UUIDUtils). Therefore, for those with strong needs, please move to the gitee code cloud. I have released the complete source code, which can be directly pulled down to modify the configuration and start directly!

Important note: the above implementation implements all the calling logic. At present, my project is using all the source code, and the Mapper database operation level is not posted. Therefore, this part needs to be written in combination with the business requirements. The specific TODO in the service implementation indicates where the database operation is required. Please refer to

Tags: Java Maven Spring Boot RESTful https

Posted on Fri, 26 Nov 2021 12:40:05 -0500 by pothole_89